{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T07:49:33.225136Z",
     "start_time": "2024-07-22T07:49:29.446561200Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:52:29.472160Z",
     "iopub.status.busy": "2025-01-21T11:52:29.471973Z",
     "iopub.status.idle": "2025-01-21T11:52:35.177077Z",
     "shell.execute_reply": "2025-01-21T11:52:35.176512Z",
     "shell.execute_reply.started": "2025-01-21T11:52:29.472139Z"
    },
    "id": "YMWskinNpYtz",
    "outputId": "841d6519-0a89-4563-cfb9-6c0d79361cd7",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:52:38.811149Z",
     "iopub.status.busy": "2025-01-21T11:52:38.810806Z",
     "iopub.status.idle": "2025-01-21T11:52:44.620239Z",
     "shell.execute_reply": "2025-01-21T11:52:44.619622Z",
     "shell.execute_reply.started": "2025-01-21T11:52:38.811125Z"
    },
    "id": "olzoueSyqofw",
    "outputId": "afd2617d-aafb-4d94-fbaa-d56fd122f22e",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Collecting kaggle\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/66/e3/c775e2213bac0ac1f1fd601d1c915d13934c5944cbff153ede6584acab50/kaggle-1.6.17.tar.gz (82 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.7/82.7 kB\u001b[0m \u001b[31m7.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25h  Preparing metadata (setup.py) ... \u001b[?25ldone\n",
      "\u001b[?25hRequirement already satisfied: six>=1.10 in /usr/local/lib/python3.10/site-packages (from kaggle) (1.17.0)\n",
      "Requirement already satisfied: certifi>=2023.7.22 in /usr/local/lib/python3.10/site-packages (from kaggle) (2024.12.14)\n",
      "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/site-packages (from kaggle) (2.9.0.post0)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.10/site-packages (from kaggle) (2.32.3)\n",
      "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/site-packages (from kaggle) (4.67.1)\n",
      "Collecting python-slugify (from kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl (10 kB)\n",
      "Requirement already satisfied: urllib3 in /usr/local/lib/python3.10/site-packages (from kaggle) (2.3.0)\n",
      "Collecting bleach (from kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl (163 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m163.4/163.4 kB\u001b[0m \u001b[31m14.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting webencodings (from bleach->kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl (11 kB)\n",
      "Collecting text-unidecode>=1.3 (from python-slugify->kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl (78 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m78.2/78.2 kB\u001b[0m \u001b[31m20.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.4.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.10)\n",
      "Building wheels for collected packages: kaggle\n",
      "  Building wheel for kaggle (setup.py) ... \u001b[?25ldone\n",
      "\u001b[?25h  Created wheel for kaggle: filename=kaggle-1.6.17-py3-none-any.whl size=105800 sha256=24c16ffa25f709d48a343f8ddb5230c3913a5802e4151037bfd2661ff3f0b8ff\n",
      "  Stored in directory: /root/.cache/pip/wheels/85/59/78/39b001621fad138a41610d830155c8b08d48649b2197e433c7\n",
      "Successfully built kaggle\n",
      "Installing collected packages: webencodings, text-unidecode, python-slugify, bleach, kaggle\n",
      "Successfully installed bleach-6.2.0 kaggle-1.6.17 python-slugify-8.0.4 text-unidecode-1.3 webencodings-0.5.1\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:52:44.621576Z",
     "iopub.status.busy": "2025-01-21T11:52:44.621283Z",
     "iopub.status.idle": "2025-01-21T11:52:44.734751Z",
     "shell.execute_reply": "2025-01-21T11:52:44.734150Z",
     "shell.execute_reply.started": "2025-01-21T11:52:44.621552Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/mnt/workspace\n"
     ]
    }
   ],
   "source": [
    "# 查看当前目录\n",
    "!pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T02:01:14.100470Z",
     "iopub.status.busy": "2025-01-21T02:01:14.100150Z",
     "iopub.status.idle": "2025-01-21T02:01:14.214430Z",
     "shell.execute_reply": "2025-01-21T02:01:14.213878Z",
     "shell.execute_reply.started": "2025-01-21T02:01:14.100449Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slothkong\n"
     ]
    }
   ],
   "source": [
    "# # 删除文件夹\n",
    "# !rm -r /content/datasets/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:52:59.885005Z",
     "iopub.status.busy": "2025-01-21T11:52:59.884649Z",
     "iopub.status.idle": "2025-01-21T11:52:59.888243Z",
     "shell.execute_reply": "2025-01-21T11:52:59.887777Z",
     "shell.execute_reply.started": "2025-01-21T11:52:59.884982Z"
    },
    "id": "L8O3V3yJqbWH",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# with的好处是在离开with的时候会自己关闭文件\n",
    "import json\n",
    "token = {\"username\":\"cskaoyan\",\"key\":\"ff99d9d7ff71704e3e761217ceec03c5\"}\n",
    "with open('/mnt/workspace/kaggle.json', 'w') as file:\n",
    "  json.dump(token, file)#json.dump类似于write\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:53:01.983705Z",
     "iopub.status.busy": "2025-01-21T11:53:01.983343Z",
     "iopub.status.idle": "2025-01-21T11:53:02.098141Z",
     "shell.execute_reply": "2025-01-21T11:53:02.097570Z",
     "shell.execute_reply.started": "2025-01-21T11:53:01.983679Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"username\": \"cskaoyan\", \"key\": \"ff99d9d7ff71704e3e761217ceec03c5\"}"
     ]
    }
   ],
   "source": [
    "!cat /mnt/workspace/kaggle.json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:53:05.257298Z",
     "iopub.status.busy": "2025-01-21T11:53:05.256945Z",
     "iopub.status.idle": "2025-01-21T11:53:06.015243Z",
     "shell.execute_reply": "2025-01-21T11:53:06.014622Z",
     "shell.execute_reply.started": "2025-01-21T11:53:05.257274Z"
    },
    "id": "Pf8ZxoKzqgzu",
    "outputId": "0face2c5-be84-4ed2-f295-70cd2401708f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- path is now set to: /content\n"
     ]
    }
   ],
   "source": [
    "# 创建存放数据集的文件夹\n",
    "!mkdir -p ~/.kaggle\n",
    "!cp /mnt/workspace/kaggle.json ~/.kaggle/\n",
    "!chmod 600 ~/.kaggle/kaggle.json\n",
    "!kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 下载dataset数据集 !kaggle datasets download -d XXX   注：xxx为网页上datasets后面的路径"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:53:10.407380Z",
     "iopub.status.busy": "2025-01-21T11:53:10.407017Z",
     "iopub.status.idle": "2025-01-21T11:54:01.940959Z",
     "shell.execute_reply": "2025-01-21T11:54:01.940406Z",
     "shell.execute_reply.started": "2025-01-21T11:53:10.407355Z"
    },
    "id": "B8hQi6ejqkAG",
    "outputId": "774e8927-4da0-492d-d7c1-cc39c774dfe3",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset URL: https://www.kaggle.com/datasets/slothkong/10-monkey-species\n",
      "License(s): CC0-1.0\n",
      "Downloading 10-monkey-species.zip to /content/datasets/slothkong/10-monkey-species\n",
      "100%|███████████████████████████████████████▉| 546M/547M [00:46<00:00, 12.9MB/s]\n",
      "100%|████████████████████████████████████████| 547M/547M [00:47<00:00, 12.2MB/s]\n"
     ]
    }
   ],
   "source": [
    "# kaggle上下载数据\n",
    "!kaggle datasets download -d slothkong/10-monkey-species"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:05.013724Z",
     "iopub.status.busy": "2025-01-21T11:54:05.013349Z",
     "iopub.status.idle": "2025-01-21T11:54:05.129053Z",
     "shell.execute_reply": "2025-01-21T11:54:05.128476Z",
     "shell.execute_reply.started": "2025-01-21T11:54:05.013698Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10-monkey-species.zip\n"
     ]
    }
   ],
   "source": [
    "!ls /content/datasets/slothkong/10-monkey-species/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:07.400039Z",
     "iopub.status.busy": "2025-01-21T11:54:07.399659Z",
     "iopub.status.idle": "2025-01-21T11:54:10.976425Z",
     "shell.execute_reply": "2025-01-21T11:54:10.975784Z",
     "shell.execute_reply.started": "2025-01-21T11:54:07.400014Z"
    },
    "id": "tporD6JYsFQ-",
    "outputId": "c264f15e-db74-4cd8-f68f-c347c83b438b",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 解压\n",
    "!unzip -o -d /content /content/datasets/slothkong/10-monkey-species/10-monkey-species.zip >/dev/null"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:12.622046Z",
     "iopub.status.busy": "2025-01-21T11:54:12.621687Z",
     "iopub.status.idle": "2025-01-21T11:54:12.735559Z",
     "shell.execute_reply": "2025-01-21T11:54:12.735006Z",
     "shell.execute_reply.started": "2025-01-21T11:54:12.622021Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n0  n1\tn2  n3\tn4  n5\tn6  n7\tn8  n9\n"
     ]
    }
   ],
   "source": [
    "!ls /content/training/training/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:14.308969Z",
     "iopub.status.busy": "2025-01-21T11:54:14.308598Z",
     "iopub.status.idle": "2025-01-21T11:54:14.422935Z",
     "shell.execute_reply": "2025-01-21T11:54:14.422337Z",
     "shell.execute_reply.started": "2025-01-21T11:54:14.308944Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n0  n1\tn2  n3\tn4  n5\tn6  n7\tn8  n9\n"
     ]
    }
   ],
   "source": [
    "!ls /content/validation/validation/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:17.543980900Z",
     "start_time": "2024-07-22T08:52:17.520817400Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:18.198262Z",
     "iopub.status.busy": "2025-01-21T11:54:18.197903Z",
     "iopub.status.idle": "2025-01-21T11:54:20.469346Z",
     "shell.execute_reply": "2025-01-21T11:54:20.468839Z",
     "shell.execute_reply.started": "2025-01-21T11:54:18.198236Z"
    },
    "id": "nnayCtXTpYt2",
    "outputId": "0055a8d0-ec63-43bd-c02c-378b3e571921",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "\n",
    "from pathlib import Path\n",
    "# 注意路径是否正确\n",
    "DATA_DIR1 = Path(\"/content/training/\")\n",
    "DATA_DIR2 = Path(\"/content/validation/\")\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR1 / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR2 / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform) # 调用父类init方法\n",
    "        self.imgs = self.samples # self.samples里边是图片路径及标签 [(path, label), (path, label),...]\n",
    "        self.targets = [s[1] for s in self.samples] # 标签取出来\n",
    "\n",
    "# 预先设定的图片尺寸\n",
    "img_h, img_w = 128, 128\n",
    "transform = Compose([\n",
    "    Resize((img_h, img_w)), # 图片缩放\n",
    "    ToTensor(),\n",
    "    # 预先统计的\n",
    "    Normalize([0.4363, 0.4328, 0.3291], [0.2085, 0.2032, 0.1988]),\n",
    "    ConvertImageDtype(torch.float), # 转换为float类型\n",
    "]) #数据预处理\n",
    "\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:15.286096100Z",
     "start_time": "2024-07-22T08:15:15.272778500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:20.470689Z",
     "iopub.status.busy": "2025-01-21T11:54:20.470177Z",
     "iopub.status.idle": "2025-01-21T11:54:20.474974Z",
     "shell.execute_reply": "2025-01-21T11:54:20.474333Z",
     "shell.execute_reply.started": "2025-01-21T11:54:20.470667Z"
    },
    "id": "oS2jYFvMpYt3",
    "outputId": "837dd61e-a898-4fa8-9d5d-51adcf2b2b47"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9']"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据类别\n",
    "train_ds.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:24.968412700Z",
     "start_time": "2024-07-22T08:15:24.960415300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:20.476045Z",
     "iopub.status.busy": "2025-01-21T11:54:20.475668Z",
     "iopub.status.idle": "2025-01-21T11:54:20.480401Z",
     "shell.execute_reply": "2025-01-21T11:54:20.479767Z",
     "shell.execute_reply.started": "2025-01-21T11:54:20.476016Z"
    },
    "id": "8SJAYnQopYt4",
    "outputId": "15f39953-ca91-40e4-d084-e5e8c1d4667c"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'n0': 0,\n",
       " 'n1': 1,\n",
       " 'n2': 2,\n",
       " 'n3': 3,\n",
       " 'n4': 4,\n",
       " 'n5': 5,\n",
       " 'n6': 6,\n",
       " 'n7': 7,\n",
       " 'n8': 8,\n",
       " 'n9': 9}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds.class_to_idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:22:41.089921600Z",
     "start_time": "2024-07-22T08:22:41.076096400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:20.482440Z",
     "iopub.status.busy": "2025-01-21T11:54:20.481968Z",
     "iopub.status.idle": "2025-01-21T11:54:20.577387Z",
     "shell.execute_reply": "2025-01-21T11:54:20.576878Z",
     "shell.execute_reply.started": "2025-01-21T11:54:20.482409Z"
    },
    "id": "o2x2L3yTpYt4",
    "outputId": "6925790a-4125-4882-9d51-d015fbd6e7eb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/content/training/training/n0/n0018.jpg 0\n",
      "tensor([[[-0.1553, -0.0236,  0.3525,  ...,  1.1425,  1.8384,  0.9920],\n",
      "         [-0.1365, -0.1365,  0.1833,  ...,  0.8416,  1.9324,  0.9356],\n",
      "         [-0.1177, -0.1741,  0.1268,  ...,  1.1425,  1.3306,  1.3494],\n",
      "         ...,\n",
      "         [ 2.1205,  2.1205,  2.0641,  ...,  0.9920,  0.8416,  0.3713],\n",
      "         [ 2.0641,  1.9136,  1.6691,  ...,  0.3713, -0.0612, -0.0048],\n",
      "         [ 1.9513,  1.6691,  1.2553,  ...,  0.0328, -0.4938, -0.1365]],\n",
      "\n",
      "        [[-0.2193, -0.0456,  0.3983,  ...,  1.3632,  2.0580,  1.0737],\n",
      "         [-0.2386, -0.2000,  0.2053,  ...,  0.9386,  2.1545,  1.1702],\n",
      "         [-0.2772, -0.2772,  0.0702,  ...,  1.1702,  1.5176,  1.6527],\n",
      "         ...,\n",
      "         [ 2.4054,  2.3668,  2.2703,  ...,  0.6491,  0.5333,  0.1088],\n",
      "         [ 2.2896,  2.1352,  1.8650,  ...,  0.0509, -0.3351, -0.3544],\n",
      "         [ 2.1352,  1.8457,  1.4211,  ..., -0.3544, -0.8369, -0.5667]],\n",
      "\n",
      "        [[-0.7283, -0.4324,  0.2975,  ...,  0.2383,  0.9287, -0.3141],\n",
      "         [-0.7678, -0.6297,  0.0805,  ...,  0.1002,  1.1851, -0.3338],\n",
      "         [-0.8467, -0.7480, -0.0971,  ...,  0.3961,  0.8103,  0.6131],\n",
      "         ...,\n",
      "         [-0.4324, -0.4916, -0.6099,  ...,  0.5934,  0.4553,  0.0213],\n",
      "         [-0.5113, -0.6099, -0.8467,  ..., -0.0379, -0.5508, -0.5508],\n",
      "         [-0.6494, -0.8467, -1.1228,  ..., -0.5705, -1.2215, -0.9650]]]) 0\n"
     ]
    }
   ],
   "source": [
    "# 图片路径 及 标签\n",
    "for fpath, label in train_ds.imgs:\n",
    "    print(fpath, label)\n",
    "    break\n",
    "\n",
    "#这个和之前的dataset完全一致\n",
    "for img, label in train_ds:\n",
    "    # c, h, w  label\n",
    "    print(img, label)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:44:58.447431700Z",
     "start_time": "2024-07-22T08:44:29.539230100Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:20.578217Z",
     "iopub.status.busy": "2025-01-21T11:54:20.577981Z",
     "iopub.status.idle": "2025-01-21T11:54:37.495187Z",
     "shell.execute_reply": "2025-01-21T11:54:37.494524Z",
     "shell.execute_reply.started": "2025-01-21T11:54:20.578197Z"
    },
    "id": "mVb-3y68pYt5",
    "outputId": "3116d220-7fb4-4b1d-d648-baed26152c4f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.0002]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[0:1, :, :].mean(dim=(1, 2))\n",
    "        std += img[0:1, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:32:54.269143900Z",
     "start_time": "2024-07-22T08:32:25.590376800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:37.496635Z",
     "iopub.status.busy": "2025-01-21T11:54:37.496054Z",
     "iopub.status.idle": "2025-01-21T11:54:54.243626Z",
     "shell.execute_reply": "2025-01-21T11:54:54.243032Z",
     "shell.execute_reply.started": "2025-01-21T11:54:37.496601Z"
    },
    "id": "ARxVczNhpYt5",
    "outputId": "b2b2706e-4c8d-494d-f317-195bc1b55ddc"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([3.6267e-05]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[1:2, :, :].mean(dim=(1, 2))\n",
    "        std += img[1:2, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:33:54.095864500Z",
     "start_time": "2024-07-22T08:33:26.014587300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:54:54.244781Z",
     "iopub.status.busy": "2025-01-21T11:54:54.244285Z",
     "iopub.status.idle": "2025-01-21T11:55:11.021850Z",
     "shell.execute_reply": "2025-01-21T11:55:11.021104Z",
     "shell.execute_reply.started": "2025-01-21T11:54:54.244751Z"
    },
    "id": "kdtwsOxIpYt5",
    "outputId": "e723ef22-4061-4795-e9c1-4c85bcb2df67"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([-6.5391e-07]), tensor([1.0002]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[2:3, :, :].mean(dim=(1, 2))\n",
    "        std += img[2:3, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:39:44.080468500Z",
     "start_time": "2024-07-22T08:39:15.435622600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:11.022779Z",
     "iopub.status.busy": "2025-01-21T11:55:11.022561Z",
     "iopub.status.idle": "2025-01-21T11:55:11.026612Z",
     "shell.execute_reply": "2025-01-21T11:55:11.026112Z",
     "shell.execute_reply.started": "2025-01-21T11:55:11.022756Z"
    },
    "id": "2U0E5JMTpYt6",
    "outputId": "ab841753-c1d0-4dfd-cd6d-375ac31e02d6"
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2)) #dim=(1, 2)表示计算均值后，宽和高消除（把宽和高所有的像素加起来，再除以总数）\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:57.696309200Z",
     "start_time": "2024-07-22T08:52:57.686315700Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:11.027580Z",
     "iopub.status.busy": "2025-01-21T11:55:11.027294Z",
     "iopub.status.idle": "2025-01-21T11:55:11.030769Z",
     "shell.execute_reply": "2025-01-21T11:55:11.030282Z",
     "shell.execute_reply.started": "2025-01-21T11:55:11.027559Z"
    },
    "id": "vzPl-CPOpYt6",
    "outputId": "51436c68-37f1-42c0-ad44-a5582069bcc1"
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 64\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:53:04.933884100Z",
     "start_time": "2024-07-22T08:52:59.755852600Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:11.032709Z",
     "iopub.status.busy": "2025-01-21T11:55:11.032521Z",
     "iopub.status.idle": "2025-01-21T11:55:13.268296Z",
     "shell.execute_reply": "2025-01-21T11:55:13.267689Z",
     "shell.execute_reply.started": "2025-01-21T11:55:11.032690Z"
    },
    "id": "PvzJPmCKpYt7",
    "outputId": "14c12450-f807-4fc7-f521-fba2d5427f88"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([64, 3, 128, 128])\n",
      "torch.Size([64])\n"
     ]
    }
   ],
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MxR9Zm8FpYt7"
   },
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.269307Z",
     "iopub.status.busy": "2025-01-21T11:55:13.269054Z",
     "iopub.status.idle": "2025-01-21T11:55:13.342414Z",
     "shell.execute_reply": "2025-01-21T11:55:13.341937Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.269284Z"
    },
    "id": "93Z27pjxpYt7",
    "outputId": "5795e337-36fb-4dc0-b9c5-9c94061d524f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 864\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 4194304\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "source": [
    "\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes=10, activation=\"relu\"):\n",
    "        super(CNN, self).__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.flatten = nn.Flatten()\n",
    "        # input shape is (3, 128, 128) so the flatten output shape is 128 * 16 * 16\n",
    "        self.fc1 = nn.Linear(128 * 16 * 16, 128)\n",
    "        self.fc2 = nn.Linear(128, num_classes)\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1qNGeGLmpYt8"
   },
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.343405Z",
     "iopub.status.busy": "2025-01-21T11:55:13.343028Z",
     "iopub.status.idle": "2025-01-21T11:55:13.443462Z",
     "shell.execute_reply": "2025-01-21T11:55:13.442990Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.343384Z"
    },
    "id": "mz4gW8hupYt8"
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vK16Ii8KpYt8"
   },
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.444359Z",
     "iopub.status.busy": "2025-01-21T11:55:13.444067Z",
     "iopub.status.idle": "2025-01-21T11:55:13.638604Z",
     "shell.execute_reply": "2025-01-21T11:55:13.638110Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.444339Z"
    },
    "id": "0RaJusY7pYt9"
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "RhHZoT43pYt-"
   },
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.639430Z",
     "iopub.status.busy": "2025-01-21T11:55:13.639178Z",
     "iopub.status.idle": "2025-01-21T11:55:13.644347Z",
     "shell.execute_reply": "2025-01-21T11:55:13.643862Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.639409Z"
    },
    "id": "X6-gIb2YpYt_"
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch.\n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BtnJ4AlcpYt_"
   },
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.645331Z",
     "iopub.status.busy": "2025-01-21T11:55:13.644907Z",
     "iopub.status.idle": "2025-01-21T11:55:13.649283Z",
     "shell.execute_reply": "2025-01-21T11:55:13.648777Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.645309Z"
    },
    "id": "qLSAWIq8pYuA"
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute\n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter\n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "colab": {
     "referenced_widgets": [
      "481dddc81fe84be3afc1853a2c537f47"
     ]
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:55:13.650127Z",
     "iopub.status.busy": "2025-01-21T11:55:13.649901Z",
     "iopub.status.idle": "2025-01-21T11:56:54.943404Z",
     "shell.execute_reply": "2025-01-21T11:56:54.942819Z",
     "shell.execute_reply.started": "2025-01-21T11:55:13.650108Z"
    },
    "id": "p9T_O6X7pYuA",
    "outputId": "4cd378c8-3792-4cf4-91b4-33d3cbe1e02b",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 80%|████████  | 288/360 [01:37<00:23,  3.08it/s, epoch=15]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 16 / global_step 288\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 80%|████████  | 288/360 [01:40<00:25,  2.85it/s, epoch=15]\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(num_classes=10, activation=activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001, eps=1e-7)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/monkeys-cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, img_h, img_w])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/monkeys-cnn-{activation}\", save_step=len(train_loader), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T11:56:54.944382Z",
     "iopub.status.busy": "2025-01-21T11:56:54.944134Z",
     "iopub.status.idle": "2025-01-21T11:56:55.165348Z",
     "shell.execute_reply": "2025-01-21T11:56:55.164877Z",
     "shell.execute_reply.started": "2025-01-21T11:56:54.944360Z"
    },
    "id": "m_D2srP_pYuC",
    "outputId": "2f735bb6-bd73-43e1-afb8-2bd472e33d0c"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0UAAAHACAYAAABtZcP3AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAw65JREFUeJzs3Xd4k1X7wPFvkqa7pS2lAyi07L2HgDJkiYjifl0IbgUX+qq8TlzoT0V9Xwc4EHHvCQJl703ZqxQodLG6Z8bz++NpSktn0qQZvT/XlavJk2fch9A0d84599EoiqIghBBCCCGEEI2U1tkBCCGEEEIIIYQzSVIkhBBCCCGEaNQkKRJCCCGEEEI0apIUCSGEEEIIIRo1SYqEEEIIIYQQjZokRUIIIYQQQohGTZIiIYQQQgghRKMmSZEQQgghhBCiUfNydgB1YTabSU1NJSgoCI1G4+xwhBCi0VAUhdzcXJo3b45WK9+jWcjfJSGEcB5H/G1yi6QoNTWVmJgYZ4chhBCN1smTJ2nZsqWzw3AZ8ndJCCGcz55/m9wiKQoKCgLUhgcHB1t9vMFgYOnSpYwZMwa9Xm/v8JzKk9sG0j535sltA89uX/m2FRYWEhMTU/Y+LFTyd6lm0j735cltA89unye3DRz/t8ktkiLL0ITg4GCb//j4+/sTHBzscf9JPLltIO1zZ57cNvDs9lXVNhkiVpH8XaqZtM99eXLbwLPb58ltA8f/bZIB4kIIIYQQQohGTZIiIYQQQgghRKMmSZEQQgghhBCiUXOLOUVCCCGEu1EUBaPRiMlkqvScwWDAy8uLoqKiKp93d+7ePp1Oh5eXl8ylE6IRkaRICCGEsLOSkhLS0tIoKCio8nlFUYiKiuLkyZMe+cHbE9rn7+9PdHQ03t7ezg5FCNEAJCkSQggh7MhsNnPs2DF0Oh3NmzfH29u7UmJgNpvJy8sjMDDQIxfFdef2KYpCSUkJZ86c4dixY7Rv397t2iCEsJ4kRUIIIYQdlZSUYDabiYmJwd/fv8p9zGYzJSUl+Pr6euQHbndvn5+fH3q9nhMnTpS1Qwjh2dzvnUoIIYRwA+6YDIgL5PUTonGR33ghhBBCCCFEoyZJkRBCCCGEEKJRk6RICCGEW1uzZg0TJkygefPmaDQafv/991qPWbVqFX369MHHx4d27doxf/58h8fZ2PTo0YP333/f2WEIIUSdSFIkhBDCreXn59OzZ08+/PDDOu1/7Ngxxo8fz4gRI0hISOCxxx7jnnvuYcmSJQ6O1PUNHz6cxx57zC7nWrFiBffee69dziWEEI4m1eeEEEK4tXHjxjFu3Lg67z9nzhzi4uJ45513AOjcuTPr1q3j3XffZezYsY4K0yMoioLJZMLLq/aPD+Hh4dVW3xNCeC6zWaHIaMLf273SDPeKVgghPJmxBP5+DArOQfvR0H4shMQ4OyqPs3HjRkaNGlVh29ixY2vsISkuLqa4uLjscU5ODgAGgwGDwVBhX4PBgKIomM1mzGYzoCYThQZT2T6KolBYYkJXbHD44qZ+el2drjFlyhRWr17N6tWry4a9ff7559x99938/fffvPDCC+zZs4fFixcTExPDE088webNm8nPz6dz58689tprZf+uiqLQo0cPHnvssbJ/V51Ox9y5c1m0aBFLly6lRYsWvPXWW1x99dW1xmYymbj//vtZuXIl6enptGrVigcffJBHHnmkwn7z5s3j3XffJTExkbCwMK677jr+97//AZCVlcUzzzzDH3/8QXZ2Nu3ateP111/nqquuqvKaZrMZRVEwGAzodLoKz1le84tfe0/gyW0Dx7TvVGYhH6w6yt6UnDofExPqx0sTOhMZbL9y7ysOpPPuHh0fHl1fp995L52GK7tFMemSVvjqdbXuXxebks7zf0sP0zkqiNcmdrXLOS3Kv3aO+P8pSZEQQriKJf+BhG/U+4cXA09AZDfocIV6a9EHtPb5w9WYpaenExkZWWFbZGQkOTk5FBYW4ufnV+mYWbNmMXPmzErbly5dWqk3xMvLi6ioKPLy8igpKQGgsMTEoNmb7NiKuts4/RL8vGv/f/Pyyy9z4MABunTpwowZMwA4ePAgAE8//TSvvPIKsbGxhISEcOrUKUaMGMEzzzyDj48P33//Pddccw1btmwhJuZCIl9cXFyWQALMnDmTmTNn8sILL/DJJ59wxx13sHv3bkJDQ2uMzWAw0KxZM+bNm0dYWBibN2/m8ccfp0mTJlx77bWAmsA999xzvPjii4waNYqcnBw2b95MTk4OZrOZK664gtzc3LKewoMHD1aKr7ySkhIKCwtZs2YNRqOxyn3i4+Nr/Xd1V57cNrBP+/INsDRFy9p0DSbFui83DmXkse/EaR7pZsLfDp/Gj2RrmHNAi1HRQF5+nY/bl5rLp6sOc2WMmf7NFLQ2fkeTmg9/Jms5kKXOzElMz6a39oRd2nax+Ph4CgoK7H5eSYqEEMIVJHwHWz9V7w98EFJ3wqktkLFXva19G/zDocNY9dZmBPgGOzfmRmTGjBlMnz697HFOTg4xMTGMGTOG4OCKr0NRUREnT54kMDCwbNFPr5KqP1Q3hKDgoDoNYwkODsbf358mTZrQvn17AFJSUgB45ZVXuOaaa8r2bd26NUOGDCl73Lt3b/755x9WrVrF1KlTURQFAB8fnwr/PlOmTOGuu+4C4K233mLu3LkcOHCAK664otb4Zs2aVXa/e/fu7Nq1i7///ps777wTgNmzZzN9+nSeeuqpsv2GDx8OqMnr9u3b2bdvHx06dADUQhA1KSoqws/Pj6FDh1ZavNVgMBAfH8/o0aPR6/W1xu5OPLltYJ/2FRlMfLkxmblrj5FbpP5uD24TxqRBdetxKTKYeP6P/aTllfBzRjhf3Nm3Tl9cVGdfag7/mbcVo2KiW6iZx8b3rtMQ15PnC/lodRJp2UV8e1TH9rxA/j2mPUPbh9e5Bzstu4h3lyfy+55UFAW8tBr+1b8l04a3oWmgj81tqkr5166wsNCu54bGkBSl7UK74xvaZmQDVzo7GiGEqCxtlzpsDmDYMzBC/Zae/HOQuAwO/wOJy6HgrNqTlPANaPUQe2lpL9JYCItzWvjuJioqioyMjArbMjIyCA4OrrKXCNQP9z4+lf/A6/X6Sh+sTCYTGo0GrVZbtgBogI+e/S9fmK9kNpvJzcklKDjI4YuE1nX4nIUldriwgOmAAQMqxJmXl8dLL73EwoULSUtLw2g0UlhYyMmTJ9FqtWXDBsufC6Bnz55lj4OCgggODubs2bN1+jf48MMPmTdvHsnJyRQWFlJSUkKvXr3QarWcPn2a1NRURo0aVeW5du/eTcuWLenUqVOd/x20Wi0ajabK19iipufcnSe3DWxrn8ms8Mv2U8yOP0x6ThEAnaODeWZcJ6sSCYDW4UHcPHcj25OzePTH3XwyqR96nfXvBUln8rh7wQ7yi00MiA3lpsgzDOsYWee23di/FV9uOM6HKxM5lJHHPV/tZFCbpsy4shM9WoZUe1x2oYGPViXyxfrjlBjV3/fx3aN5cmxH4sIDrG6HNfR6fbW9t/Xh+UlR5nF0W+fS0q+1syMRQojKCs7DD7eDsQjaj4FhT194LqAp9LxZvZkMcGIDHF6iJknnkyBppXpb/DQ061Tai3QFtBwAOs9/e7fVoEGDWLRoUYVt8fHxDBo0yGHX1Gg0FXprzGYzRm8d/t5eDk+K7CEgoOKHnCeffJL4+Hjefvtt2rVrh5+fHzfccEPZcMHqXPxBTaPRlCVQNfn+++958skneeeddxg0aBBBQUG89dZbbN68GaDaZNaitueFqImiKKw8dJo3/jnI4Yw8AFqE+PHEmA5M7NUCrQ1jzjpHBzNvcn9u/3wzKw+d4d8/7WL2Tb2sOld6dhF3fL6Fc/kldG0ezJzberN2xVKr4vDV67h/WFtu7h/DhysT+XLDCTYmnePqD9ZzVY9o/j22I62bXvj9LzKY+GrjCT5YmUh2oTqvZ0BcGDPGdaJ3q5qHwbo6z/+rGd0LgOCiU5iMxeDB33oIIdyM2QS/3ANZyRAaC9d9AtV9QNbpoc0w9XbF63D2iDrv6NBiSN4IZw6qt/Xvg18otButJkntRoFfSEO2qsHl5eWRmJhY9vjYsWMkJCQQFhZGq1atmDFjBikpKSxYsACABx54gA8++ICnnnqKu+66ixUrVvDjjz+ycOFCZzXBZXh7e2MymWrdb/369UyePLlsPk9eXh7Hjx93WFzr169n8ODBPPTQQ2Xbjh49WnY/KCiI2NhYli9fzogRIyod36NHD06dOsXhw4fLhs8JURcJJ7OYtegAm4+dB6CJn55pI9pxx6DW9S5O0C82jI9v68u9C7bxe0IqIf7evDihS516nLIKSrjj882kZBUSFx7Al3cNIMjH9i9YQvy9eXZ8F+4cHMvspYf5LSGFv3ensWRfOrcNbM3UEe1Yl3iGt5ccJiVLHbrWITKQp6/oxOWdIhxeMKYheH5SFNIKxS8UbWEm5tP7ofUAZ0ckhBCqVbPg6HLw8oObv1GTmboKb6/eBj8MhZnq8LrDi+FIvPp4z4/qTaOD1oNh4sceW8lu27ZtFT4IW+b+3HnnncyfP5+0tDSSk5PLno+Li2PhwoU8/vjjvP/++7Rs2ZLPPvtMynEDsbGxbN68mePHjxMYGFhtL0779u359ddfmTBhAhqNhueff75OPT62at++PQsWLGDJkiXExcXx1VdfsXXrVuLiLgwbfemll3jggQeIiIhg3Lhx5Obmsn79eh5++GGGDRvG0KFDuf7665k9ezbt2rXj4MGDaDSaOs1nEg0r8XQub/xziPP5xbXvbCVFUcjM1DH/1OZaP8gbTAp7UrIB8PbSMmVILA8Na0cTf/t9wT6iUwRv39iTx35IYP6G4zQN8Obhke1rPKagxMiU+Vs5cjqPyGAfFtw1gPBAH7tUZGsZ6s/sm3txz2VteGPxQdYcPsP8DcdZsPE4ZnWqIFHBvkwf3YHr+7ZEZ2tlBhfk+UmRRoMS3QtN0ko06bskKRJCuIaDi2DNW+r9q/8LUd1sP5dfKHS/Qb2ZjGqBhkP/qEPtzh6ClB0QGGGfuF3Q8OHDyyb2V2X+/PlVHrNz504HRuWennzySe688066dOlCYWEhX3zxRZX7zZ49m7vuuovBgwcTHh7O008/XW0VN3u4//772blzJzfffDMajYZbbrmFhx56iH/++adsnzvvvJOioiLeffddnnzyScLDw7nhhhvKnv/ll1948sknueWWW8jPz6ddu3a88cYbDotZ2Obk+QJu+2wzGTn2T4gu0HA8L7tue2rgut4tmT6mAy1CHDMMc2LvFmQWlDDzr/28E3+Y0ABvbr+k6mkfJUYzD369g53JWTTx07PgroHEhNl/PbAuzYNZcNcA1ieeZdY/B9ibkkOQjxcPDG/LXUPi6lUYwlV5flIEKFE9IWklmrRdzg5FCCHgbCL8dr96f+AD0OMm+51b56X2DLUeDGNeUecenTkEXvatAiQ8U4cOHdi4cWOFbZMnT660X2xsLCtWrKiwberUqRUe7969u0LluaoS16ysrDrF5ePjwxdffFEpSStfkQ7U5On++++v8hxhYWHMmzevTtcTznE2r5hJ87aQkVNM+4hAnhjTEXuPyjIZTWzfsZ2+ffqi86r9g337iEDaNAu0bxBVmDIkjvP5JfxvRSLP/7GXUH9vxveIrrCP2azw5E+7WH34DH56HfMm96djVJBD4xrSLpw/p17K9uRM2jULJDTA26HXcyarkqKPP/6Yjz/+uGzccNeuXXnhhRdqXEn8p59+4vnnn+f48eO0b9+eN998kyuvbNgqcEp0TwA0aQkNel0hhKikOE8trFCcA60GwZhXHXu9sDbqTQghXFhukYHJX2zh2Nl8WoT48dXdA4lqYr+FTS0MBgOG4wqju0S4XHW96aM7cD6/hG82J/PYDzsJ9vPisvbNAPVLhZl/7ePPXal4aTV8fHsf+rZumMIGWq2G/rFhDXItZ7JqRlbLli1544032L59O9u2bePyyy/nmmuuYd++fVXuv2HDBm655Rbuvvtudu7cycSJE5k4cSJ79+61S/B1pUSpSRFnDoLRkd2xQghRA0WBP6fBmQMQGAU3zlcLKAjRyD3wwAMEBgZWeXvggQecHZ5wsCKDiXsXbGNvSg5NA7z56u4BDkmIXJ1Go+Hla7oxvkc0BpPC/V9tJ+FkFgD/XZ7IlxtPoNHAOzf1ZHhHzx0S7SxW9RRNmDChwuPXXnuNjz/+mE2bNtG1a9dK+7///vtcccUV/Pvf/wbUxd/i4+P54IMPmDNnTj3CtlKTGEp0AXib8iFjn7oqvBBCNLSNH8K+30DrBTctgKAoZ0ckhEt4+eWXefLJJ6t87uLFcYVnMZrMPPLdTjYlnSfQx4v5UwY0yHA1V6XTaph9U09yCg2sPXKWyV9s4ZYBrfh4lVpt8aUJXbmmVwsnR+mZbJ5TZDKZ+Omnn8jPz692bYeNGzdWWAEcYOzYsfz+++81nru4uJji4gs9OpbJmwaDwabKGgajkTz/OCJy92I6tR1zRHerz+GqLP8e9qg44oqkfe7Lk9sG1rdPc2IduvgX0ACmUa9iju4DLvpvU75tnvr6CdcSERFBRIR8893YKIrCs7/tZen+DLx1Wj6Z1JfuLZs4Oyyn8/HSMef2vtz62WZ2ncwqS4geHdmeOwfHOjc4D2Z1UrRnzx4GDRpEUVERgYGB/Pbbb3Tp0qXKfdPT04mMjKywLTIykvT09BqvMWvWLGbOnFlp+9KlS/H3t63CRmf/WCJy93Jyy9/sSve8N974+Hhnh+BQ0j735cltg7q1z7fkPMMPPY+XYuJk6BB2nI6GixYPdUXx8fEUFBQ4OwwhhId6c/Ehfth2Eq0G/ntLbwa3DXd2SC4jwMeLLyb356a5G0k8ncekQa15bFTNpbpF/VidFHXs2JGEhASys7P5+eefufPOO1m9enW1iZEtZsyYUaGHKScnh5iYGMaMGWNTN7rBYGDvT1sBaK0/T4sGLvTgSAaDgfj4eEaPHu1yEwbtQdrnvjy5bWBF+4zF6L66Gq0xFyWiG1GTv+NKvf3Lp9pT+bYVFhY6OxwhhAf6ZM1R5qxWe0BmXdedK7rJcOKLhQV48/vUIRxIy6Fvq1CPWCDVlVmdFHl7e9OuXTsA+vbty9atW3n//feZO3dupX2joqLIyMiosC0jI4OoqJr/4/v4+ODjU7l8rF6vt/nDVZZfLACa0wfQa8weV562Pv827kDa5748uW1Qh/YteQpSt4NvCJp/fY3e332Ghuj1eoxGo7PDEEJ4mJ+2neT1RQcBeGZcJ27u38rJEbmuQB+vRlH5zRVYVX2uKmazucL8n/IGDRrE8uXLK2yLj4+vdg6SIxV6h6P4hYLZoBZbEEIIR9v5NWybB2jg+s8gLM7ZEQkhhFMt3ZfOM7/uAeC+oW14YFhbJ0ckhMqqnqIZM2Ywbtw4WrVqRW5uLt9++y2rVq1iyZIlAEyaNIkWLVqULab26KOPMmzYMN555x3Gjx/P999/z7Zt2/jkk0/s35LaaDQoUT3RHFsFaQlSgU4I4VipO+Hv0mHAI/4D7Uc7Nx4hhHCyTUnnmPbdTkxmhRv6tmTGuE7ODkmIMlb1FJ0+fZpJkybRsWNHRo4cydatW1myZAmjR6t/7JOTk0lLSyvbf/DgwXz77bd88skn9OzZk59//pnff/+dbt262bcVdWRZxJXUBKdcXwjRSOSfgx8mgakYOoyDy6ouNSyEp4mNjeW9995zdhjCBR1Kz+XeL7dRYjQzqnMkb1zXXebICJdiVU/R559/XuPzq1atqrTtxhtv5MYbb7QqKEcpW8Q1LcGpcQghPJjZBL/cBdnJENYGrp0D2nqPVBZCCLf2+qID5BYbGRAXxge39sZLJ++LwrXYvE6RO1Kie6l3MvaDsdjjii0IIWphNsPBv6E4F2IvhdDW9r/GilchaRXo/eHmr8EvxP7XEEIIN3IgLYfVh8+g1cDbN/TEV69zdkhCVNK40vQmMSDFFoRonNL3wOej4cc74I+H4P0e8F53+GMq7PoBclLrf40Df8G62er9q/8HkV3rf07hGRQFSvIr3gwFlbc54qYodQrxk08+oXnz5pjN5grbr7nmGu666y6OHj3KNddcQ2RkJIGBgfTv359ly5bZ/E8ye/ZsunfvTkBAADExMTz00EPk5eVV2Gf9+vUMHz4cf39/QkNDGTt2LJmZmYBa6On//u//aNeuHT4+PrRq1YrXXnvN5niE43yyJgmAcd2jadXUtZckEI1Xo+opQqOB6F6QtFKKLQjRWBTnwapZsOljUEzgHQQRndRCCFnJaoW4nV+r+zZtB3FD1VvsZRBgxUKCZw7Dbw+q9y95CLrfYP+2CPdlKIDXm5c91AIhDXXt/6SCd0Ctu9144408/PDDrFy5kpEjRwJw/vx5Fi9ezKJFi8jLy+PKK6/ktddew8fHhwULFjBhwgQOHTpEq1bWl1TWarX897//JS4ujqSkJB566CGeeuopPvroIwASEhIYOXIkd911F++//z5eXl6sXLkSk8kEqMWfPv30U959910uvfRS0tLSOHjwoNVxCMdKySrkr13ql073D23j5GiEqF7jSooAmvdSkyIptiCE5zvwN/zzFOSkqI+7TIQr3oDgaHUIXfImOLZGvaXtgnOJ6m3bPHX/iK4Qd5maIMUOUXuaq1KcCz/cDiW50HoIjH65QZonhD2FhoYybtw4vv3227Kk6OeffyY8PJwRI0ag1Wrp2bNn2f6vvPIKv/32G3/++SfTpk2z+nqPPfZY2f3Y2FheffVVHnjggbKk6P/+7//o169f2WOArl3V3tfc3Fzef/99PvjgA+68804A2rZty6WXXmp1HMKx5q07htGsMLhtU3q0DHF2OEJUq/ElRZZ5RVJsQQjPlZUM/zwNhxapj0Naw/h3KpbF9glSH1u2FWbCiQ1wbK2aJJ3ed+G2eQ6ggegepT1JwyC6r3qcoqhD8M4egqBouHE+6Dx3sVphI72/2mNTymw2k5ObS3BQEFpHF+LQ13240m233ca9997LRx99hI+PD9988w3/+te/0Gq15OXl8dJLL7Fw4ULS0tIwGo0UFhaSnJxsU1jLli1j1qxZHDx4kJycHIxGI0VFRRQUFODv709CQkK1hZoOHDhAcXFxWfIm7MNkVvhjVxrLTmq53GCq98Lb2QUGvtui/v+4T3qJhItrfElR817qTym2IITnMRlg00ew6g11uJJWD0MeUUtie9fywdAvFDqNV28A+Wfh+NoLSdK5I2pvUtou2PA/vDQ6LvOLRVf4Exz8S73WTQsgMMLx7RTuR6OpOITNbAa9Sd3mQtUJJ0yYgKIoLFy4kP79+7N27VreffddAJ588kni4+N5++23adeuHX5+ftxwww2UlJRYfZ3jx49z1VVX8eCDD/Laa68RFhbGunXruPvuuykpKcHf3x8/P79qj6/pOWE9RVFYffgMb/xzkIPpuYCWmBVHefaq+s2L/HrzCQpKTHSKCmJYh2b2CVYIB2l8SVFIa/XDT2EmnN4PzXs7OyIhhD0kb4a/H1d7dkAdxjZ+tjp/yBYB4dD1WvUGkJNWmiSpw+00WScIKzgKB4+qz497A2IG1L8dQjiRr68v1113Hd988w2JiYl07NiRPn3U+bfr169n8uTJXHut+juRl5fH8ePHbbrO9u3bMZvNvPPOO2U9ZT/++GOFfXr06MHy5cuZOXNmpePbt2+Pn58fy5cv55577rEpBqHacyqbWf8cYMPRcwD4e+soKDHxxYYT3NCvFR2jgmw6b5HBxBfrjwNw/7A2siaRcHmNLykqX2whNUGSIiHcXcF5WPYS7PhSfewXBmNehV63qr/v9hIcDT1uUm+A4cxR9v71ET1D8tFGdIR+d9vvWkI40W233cZVV13Fvn37uP3228u2t2/fnl9//ZUJEyag0Wh4/vnnK1Wqq6t27dphMBj43//+x4QJE1i/fj1z5sypsM+MGTPo3r07Dz30EA888ADe3t6sXLmSG2+8kfDwcJ5++mmeeuopvL29GTJkCGfOnGHfvn3cfbf8LtZF8rkC3lp6qKwIgrdOy6RBrbnvstbcPWcFezK1PP/7Xn64/xKbEprfdqZwNq+Y5k18uapH89oPEMLJXKfPviFZhtCl7nRqGEKIelAU2PU9fND/QkLU+3Z4eDv0vs2+CVFVQlqR3HQopgn/g0sfd/z1hGggl19+OWFhYRw6dIhbb721bPvs2bMJDQ1l8ODBTJgwgbFjx5b1IlmrZ8+ezJ49mzfffJNu3brxzTffMGvWrAr7dOjQgaVLl7Jr1y4GDBjAoEGD+OOPP/DyUr/Pff7553niiSd44YUX6Ny5MzfffDOnT5+2veGNxPn8Emb+tY+Rs1fx165UNBq4tncLlj8xjOeu6kKovzfXxZnx02vZcvw8P28/ZfU1zGaFT0vLcN91aRx6WahVuIHG11MEUmxBCHd39og6VO74WvVxs05w1bvQerBz4xLCA2i1WlJTK6/bFRsby4oVKypsmzp1aoXHluF0delBevzxx3n88ccrbLvjjjsqPB42bBjr16+vNs5nn32WZ599ttZrCSgsMTFv/THmrDpKbrERgMvah/P0FZ3o1qJJhX3DfGDaiLa8tfQIs/45yKjOkYQGeNf5WvEHMkg6m0+wrxf/GmB9uXYhnKFxJkVSbEEI92QohLWzYf17YCoBLz8Y9hQMmgZedf+DLYQQjcnKg6d55tfdZOQUA9AlOpgZV3bisvbVFz+YMrg1f+xK43BGHv+35CCzrutRp2spisKc1epcy9svaU2gT+P8qCncT+PszwxpDb4hYDaoxRaEEK4vcTl8NAjW/J+aELUfA1M3wWXTJSESwsV88803tGzZkuDgYAIDAyvcLGsNiYZhNJl59PudZOQU0yLEj/du7sXfD19aY0IEoNdpeXVidwC+23KS7Scy63S9bScy2ZmchbdOy+QhsfUNX4gG0zjTd41GLbAgxRaEcH25GbBkBuz9RX0cFA3j3oTOV8s8HiFc1NVXX03Xrl0JDAystA5Tfde+EdbZnZJNTpGRYF8vlj8xDF+9rs7HDogL44a+Lfl5+yme+30vf00bglct84PmrlbnEl3ftwURQb71il2IhtQ4kyJQh9AlrZR5RUK4spJ8+PRyyDkFGi0MuB9G/Ad8g50dmRCiBkFBQbRp04bg4GDHL04rarQh8SwAg9o2tSohspgxrhPx+zM4kJbD/A3Hueey6hdhTTydy7IDGWg01LifEK6o8b5TWYotSAU6IVzXqa1qQuQfDveuVNcCkoRIuAlFUZwdgqgHT3n91ieq6w9d2i7cpuObBvrwzDh1vbd34w+Tnl1U7b6flFacG905krbNAm26nhDO0niToouLLQghXE/KdvVn3NALv7NCuDjL8LCCggInRyLqw/L6ufNwv8ISU9lcoME2JkUAN/eLoU+rEPJLTLzyd9VzsTNyivh9p1q18P5hbW2+lhDO0niHz1mKLRRlqcUWZF6REK4nZYf6s4Vta6EI4Qw6nY6QkJCyNXP8/f0rLX5pNpspKSmhqKjII4eXuXP7FEWhoKCA06dPExISgk5n/ZAzV7HtxHlKTGaign1pEx5g83m0Wg2vTuzOVf9by8I9adx0+AzDOlQs1PDF+uOUmMz0jw2lb+vQ+oYuRINrvEmRRlM6r2iVFFsQwlVZhrc2l6RIuJeoqCiAahcTVRSFwsJC/Pz8KiVMnsAT2hcSElL2Orory9C5we2a1vt16NI8mMmD45i3/hgv/LGXJY8NLZujlFtk4JtNJwC4b6j0Egn31HiTIiitQLdKii0I4Ypy0yEnRS2wEN3T2dEIYRWNRkN0dDQREREYDIZKzxsMBtasWcPQoUPdenhWddy9fXq93q17iCw2HFWLLNg6n+hi08d0YOGeVE6cK+CjVUeZProDAN9vOUlusZG2zQIY2SnCLtcSoqE17qSorNhCgjOjEEJUxTJ0rlkn8JEJu8I96XS6Kj9c63Q6jEYjvr6+bpk01MbT2+cOsgpK2JOSDcAQOyVFgT5evHBVV6Z+u4M5q44ysVdzWob68/m6YwDcP7QtWq179gwK4V4Dfe2trNjCPim2IISrSS1NimTonBBCWG1T0jkUBdo2CyAy2H7rBV3ZPYqhHZpRYjLzwh/7+CMhhfScIiKCfLimd3O7XUeIhta4kyJLsQWzQS22IIRwHVJkQQghbGaZT2SvXiILjUbDy1d3xdtLy7rEs7z8l/r5acqQOHy83H/IoWi8GndSZCm2ADKETghXoigXeookKRJCCKutL51PZO+kCCA2PICpw9sBkFtsJNDHi1sHtrL7dYRoSI07KYIL84qk2IIQriPzGBRmgs4bIro6OxohhHAradmFJJ3JR6uBS9o0dcg1HhjehrjSMt+3DIihiZ/MHRPurXEXWgDpKRLCFVmGzkX1AC9v58YihBBuxjJ0rnuLJg5LVny8dHx2Zz/+TEjlvqFtHHINIRqSJEWW9YlO7wdjiXwAE8IVyHwiIYSw2YZEdejcYAcMnSuvbbNAHi8tyy2Eu5Phc5ZiC6YSKbYghKuQynNCCGETRVHK5hPZa30iIRoDSYoqFFvY6dRQhBCAyQhpu9T70lMkhBBWOXomn4ycYry9tPRtHerscIRwG5IUgRRbEMKVnDkIhgLwDoKm7Z0djRBCuJX1pUPn+rUOxVcvJbKFqCtJikCKLQjhSsqGzvUCrbxFCSGENSxJkSNKcQvhyeQTB1zoKbIUWxBCOI8UWRBCCJuYzAqbkhyzaKsQnk6SIoDQWCm2IISrKFu0ta9z4xBCCDezNyWbnCIjQb5edG/RxNnhCOFWJCmCisUWZF6REM5jKIKMfep9qTwnhBBWWVc6dO6SNk3RaTVOjkYI9yJJkYVlCJ1UoBPCedL3gNkIAc2gSUtnRyOEEG5lQ2kp7iFtmzo5EiHcjyRFFlJsQQjnKz90TiPfcgohRF0VGUxsO54JwKXtZT6RENaSpMhCii0I4Xwp29WfMnROCCGssuNEJsVGMxFBPrRtFujscIRwO5IUWUixBSGcTyrPCSGETdaVK8WtkZ52IawmSZGFFFsQwrmKsuHcEfW+9BQJIYRV1h9VS3EPlvlEQthEkqLyyootJDgzCiEaJ0uRk5DWECB/1IUQoq6yCw3sOZUFyPpEQthKkqLypKdICOeRoXNCCGGTzUnnMCvQJjyA5iF+zg5HCLckSVF5lp6ijH1SbEGIhmapPCdD54QQwirrS+cTDW4nvexC2EqSovKk2IIQzpNSrhy3EEKIOrPMJxrSVobOCWErSYrK02gguqd6X4bQCdFwcjMgJwU02gu/g0IIIWqVkVNE4uk8NBoYJEUWhLCZJEUXa95b/SnFFoRoOJahc+EdwUfW1xBCiLracFQdOteteRNC/L2dHI0Q7kuSootJsQUhGp4MnRNCCJusTywtxS3ziYSoF0mKLibFFoRoeCnb1Z8tejs3DiGEcCOKopQVWZD5RELUj1VJ0axZs+jfvz9BQUFEREQwceJEDh06VOMx8+fPR6PRVLj5+vrWK2iHkmILQjQsRZHKc0IIYYNjZ/NJyy7CW6elf2yYs8MRwq1ZlRStXr2aqVOnsmnTJuLj4zEYDIwZM4b8/PwajwsODiYtLa3sduLEiXoF7VBSbEGIhpV5HAozQecNkd2cHY0QQrgNS9W5Pq1D8PPWOTkaIdyblzU7L168uMLj+fPnExERwfbt2xk6dGi1x2k0GqKiomyL0Bma94Jjq9ViCzLFQQjHsgydi+oOXjJJWAgh6mqDDJ0Twm6sSooulp2dDUBYWM1dtnl5ebRu3Rqz2UyfPn14/fXX6dq1a7X7FxcXU1xcXPY4JycHAIPBgMFgsDpOyzF1PVYT0R0vwJy6E5MN12tI1rbN3Uj73Fdd26Y9tQ0dYIrqhdmN/h0ay2vnie0TwhOYzAobjlqKLEhSJER92ZwUmc1mHnvsMYYMGUK3btUPeenYsSPz5s2jR48eZGdn8/bbbzN48GD27dtHy5Ytqzxm1qxZzJw5s9L2pUuX4u/vb2vIxMfH12k//+IsRgNK+l7++ftPFG29cscGUde2uStpn/uqrW1DDq8gHNh1RsvJRYsaJig78vTXrqCgwNlhCCGqsD81h+xCA4E+XvRs2cTZ4Qjh9mz+tD916lT27t3LunXratxv0KBBDBo0qOzx4MGD6dy5M3PnzuWVV16p8pgZM2Ywffr0ssc5OTnExMQwZswYgoODrY7VYDAQHx/P6NGj0ev1tR+gKChJr6IrymJcv1iI6mH1NRuK1W1zM9I+91WntpmNeO19AIDu46bQPbxDA0ZYP43ltSssLHR2OEKIKqwvXZ/okjZheOmkmLAQ9WVTUjRt2jT+/vtv1qxZU21vT3X0ej29e/cmMTGx2n18fHzw8fGp8tj6fPiw6vjonnBsNfrTeyDG9ScW1fffxtVJ+9xXjW3LOAyGAvAOQh/ZGbTu94fd0187o9Ho7DCEEFWwlOIeLPOJhLALqz6BKIrCtGnT+O2331ixYgVxcXFWX9BkMrFnzx6io6OtPrZBWRZxTU1wZhRCeDbLoq3Ne7llQiSEEM6QU2Rg6/HzAAyR+URC2IVVn0KmTp3K119/zbfffktQUBDp6emkp6dXGF4xadIkZsyYUfb45ZdfZunSpSQlJbFjxw5uv/12Tpw4wT333GO/VjiCZRFXKcsthONY1idqIesTifr78MMPiY2NxdfXl4EDB7Jly5Ya93/vvffo2LEjfn5+xMTE8Pjjj1NUVNRA0Qphu283J1NkMNM+IpAOkYHODkcIj2DV8LmPP/4YgOHDh1fY/sUXXzB58mQAkpOT0Zb7xjczM5N7772X9PR0QkND6du3Lxs2bKBLly71i9zRLD1FGfvAWCKlgoVwBEs5blm0VdTTDz/8wPTp05kzZw4DBw7kvffeY+zYsRw6dIiIiIhK+3/77bc888wzzJs3j8GDB3P48GEmT56MRqNh9uzZTmiBEHVTbDTxxfpjANw3tA0ajcbJEQnhGaxKihRFqXWfVatWVXj87rvv8u6771oVlEsIjQPfJlCUDWcOXFjQVQhhH4Yi9UsHkJ4iUW+zZ8/m3nvvZcqUKQDMmTOHhQsXMm/ePJ555plK+2/YsIEhQ4Zw6623AhAbG8stt9zC5s2bGzRuIaz1R0IqGTnFRAb7cE2vFs4ORwiPIYP4q6PRXBhCJ/OKhLC/jL1gNoJ/ODSJcXY0wo2VlJSwfft2Ro0aVbZNq9UyatQoNm7cWOUxgwcPZvv27WVD7JKSkli0aBFXXnllg8QshC3MZoVP1iQBcPelcXh7ycc4IezF9RfgcabmveDY6tJ5RXc6ORghPIxl6FyLvuqXEELY6OzZs5hMJiIjIytsj4yM5ODBg1Uec+utt3L27FkuvfRSFEXBaDTywAMP8J///KfK/Z29qLi7kfY5xopDZ0g8nUegjxc39G7ukOvLa+e+PLlt4PiFxSUpqklZT9FOp4YhhEdKkSILwnlWrVrF66+/zkcffcTAgQNJTEzk0Ucf5ZVXXuH555+vtL+zFxV3V9I++/rvXh2gYWDTEtauWOrQa8lr5748uW3guIXFJSmqiRRbEMJxLJXnpMiCqKfw8HB0Oh0ZGRkVtmdkZBAVFVXlMc8//zx33HFHWSXU7t27k5+fz3333cezzz5boWAQuMCi4m5G2md/O5OzOLpxC3qdhpm3DScy2Nch15HXzn15ctvA8QuLS1JUEym2IIRjFGXD2cPqfekpEvXk7e1N3759Wb58ORMnTgTAbDazfPlypk2bVuUxBQUFlRIfnU4HVF1UyCUWFXdD0j77+XzDCQAm9mpBy6ZBDr+evHbuy5PbBo5bWFxm6NVEii0I4RiW36eQVhAgCw+K+ps+fTqffvopX375JQcOHODBBx8kPz+/rBrdxWvoTZgwgY8//pjvv/+eY8eOER8fz/PPP8+ECRPKkiMhXEXSmTyW7ld7Qu8b2sbJ0QjhmaSnqDZSbEEI+5Ohc8LObr75Zs6cOcMLL7xAeno6vXr1YvHixWXFFy5eQ++5555Do9Hw3HPPkZKSQrNmzZgwYQKvvfaas5ogRLU+XXsMRYFRnSNoH+n4XiIhGiNJimojPUVC2F9Z5TlJioT9TJs2rdrhchevoefl5cWLL77Iiy++2ACRCWG7M7nF/LLjFAD3D2vr5GiE8FwyfK42ZcUW9qrFFoQQ9ZdSWtGxRV/nxiGEEC7uyw3HKTGa6d0qhH6tQ50djhAeS5Ki2liKLZhK1GILQoj6yTsNOacAjRQvEUKIGuQXG1mw8TgA9w9ti0bWdBPCYSQpqo2m3Ac3GUInRP1Z1idq1hF8ZGy8EEJU5/utJ8kpMtImPIDRXSJrP0AIYTNJiuqieW/1Z1qCU8MQwiOUzSeSoXNCCFEdg8nM52uTALjnsjbotNJLJIQjSVJUF1JsQQj7Kas819u5cQghhAtbuDuN1OwiwgO9ua5PC2eHI4THaxRJ0fn8ehZIKCu2sA9MhnrHI0SjpSgXhs9J5TkhhKiSoijMWX0UgClD4vDVy9pZQjiaxydFiafzGPx/q5lzQMs/e9MpNpqsP0lZsYViOC3FFoQHMZbAj3dCfAOVJc48DoXnQecNkd0a5ppCCOFm1hw5y8H0XPy9ddw+sLWzwxGiUfD4pGjj0bOYzAoHsrQ88sNuBry2nBf+2MueU9koilK3k1QotrDTccEK0dCOrYb9v8P69+D4esdfzzJ0LrIbePk4/npCCOGGPlmj9hL9q38rmvjrnRyNEI2DxydFdwyKZdljlzKmhZmoYB+yCw0s2HiCCR+sY9z7a/lsbRLn8oprP5FlXpEUWxCeJHHZhfvLZ6rD2xxJhs4JIUSN9pzKZn3iOXRaDXdfFufscIRoNDw+KQJo3dSf8a3MrHpiKAvuGsCEns3x9tJyMD2XVxceYODry7lvwTbi92dgMJmrPollXpEUWxCepHxSdHIzHF7i2OtZkqLmkhQJIURV5pb2El3dszktQvycHI0QjYeXswNoSDqthqEdmjG0QzOyCwz8uTuVn7efYtfJLJbuz2Dp/gzCA715fHQHbrt4DK+lUpal2IJOurOFmzt/DM4lgtYL+kyCbfNgxSvQfgxoHfB9idkEabvU+1KOWwghKkk+V8CiPWkA3HtZGydHI0Tj0ih6iqrSxF/PHZe05o+pQ1j6+FDuG9qG8EAfzuaV8NrCA5V7jKTYgvA0ll6imEvg8ufBJxgy9sLeXxxzvTOHwJAP3oEQ3t4x1xBCCDf2+bokzAoM7dCMLs2DnR2OEI1Ko02KyusQGcR/ruzMxhmXE+qvp6DExJ6U7Io7lS+2IPOKhCdIXK7+bDcS/MNgyCPq45WvqlXp7M2yaGt0L9BKeVkhhCjvSEYuP2w7CcADQ6WXSIiGJklROXqdloFxTQHYlHSu8g5li7hKBTrh5ozFcGyNer/dKPXnwAchoJlaNnvnAvtfM1WKLAghRFVOZRZwx+dbKDKYGRgXxqC2TZ0dkhCNjiRFF7mkTRgAG49WkRRJsQXhKZI3qUPZAiMhqru6zScQhv5bvb/6LSgpsO81pfKcEEJUci6vmEmfbyE9p4h2EYHMub0vGo3G2WEJ0ehIUnSRS0q/ndl2PLPyvCJLT5Gl2IIQ7soyn6jtSHVoqEXfydCkFeSlw5a59rueoUj9vQGpPCeEEKXyio1M/mIrSWfzaRHix1d3DyA0wNvZYQnRKElSdJEOEUGEBXhTaDCx+9RF84rC2oCPFFsQHsAyn6j9qIrbvXxgxH/U++vehcJM+1wvYy+YDeAfDiGt7HNOIYRwY0UGE/ct2MaelGzCArz56u4BRDeREtxCOIskRRfRajUMjFOH0FWaV6TRQHMptiDcXE4qnN4HGi20GVH5+R43QbPOUJQN6/9rn2uWHzonw0KEEI2cyazw2PcJbDh6jgBvHV9OGUCbZoHODkuIRk2Soipc0qYuxRYSGiweIezKMnSuRV+16tzFtDq4/Dn1/uY5kJtR/2umyqKtQggBoCgKz/62h8X70vHWafl0Uj+6t2zi7LCEaPQkKaqCJSmqcl6RZRHXA39C9qkGjkwIO7AkRe1GVb9Pp/HQoh8YCmDNW/W/pqUctxRZEEI0cm8tOcT3W0+i1cB/b+nF4Hbhzg5JCIEkRVVqHxFYbl5RVsUnO1wBEV0g/wx8ezMU5zolRiFsYjLC0VXq/ZqSIo0GRr2o3t/+BZw/Zvs1i3Ph7BH1vvQUCSEasc/WJvHRqqMAvHZtd67oFu3kiIQQFpIUVaHivKLzFZ/09odbf4CACHXy+E9T1A+aQriDlG1QnA1+YRd6PasTN1Sdc2Q2wqpZNl9Sk5YAKGpVu8BmNp9HCCHc2S/bT/HqQrVI01NXdOSWAVJ0RghXIklRNWqcVxTSCm75Hrz8IDEelsxo4OiEsFFZKe7L1blDtRn5gvpz948XSmpbSWNZ7LhFLUmYEEJ4qGX7M3jql90A3HNpHA8Oa+vkiIQQF5OkqBrl5xWVGM2Vd2jZF64rXcdlyyewaU4DRieEjeoyn6i8Fn2gyzWAAitetemSmjRLUtTXpuOFEMKdbU46x9Rvd2AyK1zXpwX/ubKzLM4qhAuSpKgaHSIvzCvak5JV9U5droFRM9X7S2bAocUNFp8QVss7A5Zem7aX1/24Ec+p5bsPLYLkzVZftqynSOYTCSEamfTsIu5ZsI1io5lRnSN48/oeaLWSEAnhiiQpqoZGo+GSNtXMKypvyKPQZxIoZvj5Lkjb3UARCmGloyvUn1E9ICiy7sc16wC9blXvL38ZFKXOh/oYstHknAI00LxX3a8phBAe4K9dqeQWGekSHcwHt/ZBr5OPXUK4KvntrEGN84osNBoYPxvihoEhX61Il5PaQBEKYQVrh86VN+wZ0HnDiXVwdHmdDwspSFLvNOsIPkHWX1cIIdxY/AF1nbcb+7XEV1+HeZxCCKeRpKgGtc4rstDp4aYFEN4RclNLS3XnNVCUQtSB2XwhmbElKQqJgf73qveXv6yery6HFZSW8pahc0KIRuZ8fgnbjqsjTUZ1tqJ3XgjhFJIU1aDG9You5hcCt/0I/uGQvht+uQfMpoYIU4japSVAwTnwCYaYAbad47Lp4B0Iabtg/+91OiTU0lMki7YKIRqZlQdPY1agU1QQMWH+zg5HCFELSYpqUHFeUQ1D6CxCY+GW70DnA4f/gaXPOzZAIeoqsbSXqM0wtWfTFgHhMGiaen/la7Wvz6UohOSXJkXSUySEaGTi96tD58Z0kV4iIdyBJEW1uDCvqIZiC+XFDIBrP1bvb/oQtn7moMiEsEJ95hOVN2gq+DeFc4mQ8E3N+2Yn42PKQ9HqIapb/a4rhBBupMhgYs2RMwCMkqRICLcgSVEtBlnmFZ04X/O8ovK6XQ+XP6feX/QUHFnmoOiEqIPCTDi1Rb3fdmT9zuUbDJc9od5f9QYYCqvd1VKKW4nsCl4+9buuEEK4kY1Hz1FQYiIy2IfuLZo4OxwhRB1IUlSLdhGBNA3wpshgrn1eUXmXPQk9bwXFBD9Nhox9jgpRiJolrVJLxjfrpBZMqK9+d0NwS7WoSA09oZrUHQAo0b3rf00hhHAjlqpzozpHykKtQrgJSYpqoc4rUnuLNh6tw7yiCwfChPeh9aVQkqtWpMvNcFCUQtTAXkPnLPS+MPxp9f7a2VCUU+VumrTSniKZTySEaETMZoVlpfOJRsvQOSHchiRFdVBWbOGYFUkRgJc33PwVNG0H2Sfhu39BSYEDIhSiGopyochCu3oOnSuv563QtD0UnoeNH1R+3mxCU7qQsdJceoqEEI3HnpRsTucWE+CtY1Dbps4ORwhRR5IU1YGlp2j7iUyKjVaW2fYPg1t/BL8wSN0Bv91X5zVehKi30/shNw30/tBqsP3Oq/O6MG9uwweQd6bi82cOoTHkY9T6qMmTEEI0EstKh84N69gMHy9ZsFUIdyFJUR1UnFeUbf0JmraFf30DOm848Bcsf8nuMQpRJcvQudjL1GFv9tTlGojuBYZ8WPtOxedK5xNl+ceBVj4UCCEaD0spblmwVQj3YlVSNGvWLPr3709QUBARERFMnDiRQ4cO1XrcTz/9RKdOnfD19aV79+4sWrTI5oCdofy8ok3WzCsqr/VguLp0mNH692H7fPsEJ0RN7D2fqDyNBka9qN7f9jlkJV94LkVNijL94+x/XSGEcFEnzxdwMD0XnVbDiI4Rzg5HCGEFq5Ki1atXM3XqVDZt2kR8fDwGg4ExY8aQn59f7TEbNmzglltu4e6772bnzp1MnDiRiRMnsnfv3noH35AuKR0XbPW8ovJ63gzDnlHv/z0djq60Q2RCVKM4D05sVO/bcz5ReW1GqL1QphJY9eaF7WU9RW0cc10hhHBBll6ifq1DCQ3wdnI0QghrWJUULV68mMmTJ9O1a1d69uzJ/PnzSU5OZvv27dUe8/7773PFFVfw73//m86dO/PKK6/Qp08fPvigisnZLmxQabEFm+YVlTf8Geh+k1qq+8c74fRBO0UoxEWOrQGzAULj1CGcjqDRwMjS3qJd36r/n43FkK5+6SFJkRCiMbHMJ5Kqc0K4H6/6HJydrc6vCQsLq3afjRs3Mn369Arbxo4dy++//17tMcXFxRQXF5c9zslRS/4aDAYMBoPVcVqOseVYi1YhPjQN8OZcfgnbj52jf2yozefiynfRZSWjPbkJ5ZsbMU5ZAgHNbDqVPdrmyqR9ttMeXooOMLW5HLMj//2ieqHrMA7t4X8wL38F86BH8DIbUPzCKPAOl9fODZVvmye2TwhHyC4wsPnYeUCSIiHckc1Jkdls5rHHHmPIkCF069at2v3S09OJjKz45hAZGUl6enq1x8yaNYuZM2dW2r506VL8/f1tDZn4+HibjwVo5avlXL6Wr5du4kxLpV7n8g65g8sykgjMTib3k6tY334GZq3tXe31bZurk/ZZSVEYtf8vAoCtmcFkOHgeX5D2MkawGO2hvzl1JotWwGl9S9Bo5LVzY/Hx8RQUyDICQtTFqsOnMZkV2kcE0rppgLPDEUJYyeakaOrUqezdu5d169bZMx4AZsyYUaF3KScnh5iYGMaMGUNwcLDV5zMYDMTHxzN69Gj0er3NcWWGn2TnXwfI1Dfjyiv71fk4s1nhveWJnMsv4ZWru6DVlq5ufa4PyvxxhBUcZfy5TzGPehmlRV+rYrJX21yV27fPUIhu0eMo4Z0wD3ms8tOOat+5RPQJZ1F03vS9/jHwdvwfaOXPXWj2/ECr8+p7Qli3UVCI+752tXD7/5s1KN+2wsJCZ4cjhMMdSs9l5l/7eGRk+7LCStZaKgu2CuHWbEqKpk2bxt9//82aNWto2bJljftGRUWRkZFRYVtGRgZRUVHVHuPj44OPj0+l7Xq9vl4fPup7/KXtmwEH2JGchVmjrfP6A7P+OcDcNccAuO2SWHrGhKhPRHWBm7+Gb25Ae2oz2vljoeN4df2XyC5WxVbftrk6t21fwpew92cAdG2HQ0z/Kneze/uOrwZA03ow+oAQ+523Jpc/C/t+VecxAZqW/eCI0X1fuzry5Pbp9XqMRqOzwxDC4b7fmsyGo+dIOpPPsieGEehj3cejEqOZ1YfU9dpGSVIkhFuyqtCCoihMmzaN3377jRUrVhAXV3u53UGDBrF8+fIK2+Lj4xk0aJB1kbqAts0CCQ/0odhoZtfJuq1X9MX6Y8xdnVT2eE/KRcfFXQbTtkKv20GjhUML4ePB8Ov9kHncjtGLBmc2w8aPLjxe/HTDLdzryFLc1QltDf3uKnuoNO/dcNcWQoh6OHpGraKbnlPEu/GHrT5+87Fz5BUbCQ/0oVfLEDtHJ4RoCFYlRVOnTuXrr7/m22+/JSgoiPT0dNLT0ysMr5g0aRIzZswoe/zoo4+yePFi3nnnHQ4ePMhLL73Etm3bmDZtmv1a0UDU9YrUohKbkmovzb1wdxov/70fgFZh6lyovRcnRQAhrWDih/DQJuh8NaDA7u/hf/1g4ZOQW/38K+HCDi+G80fBpwl4B0LKdtjzk+OvayiE42vV+w2ZFAEMfRKCW6plum0sHiKEEA0t6Uxe2f35G46zPzXHquMvLNgacWGIvBDCrViVFH388cdkZ2czfPhwoqOjy24//PBD2T7JycmkpaWVPR48eDDffvstn3zyCT179uTnn3/m999/r7E4gysrW8S1lqRoU9I5Hv8hAUWBSYNa88y4TgDsTa2hh6lZR7j5K7h3pbr+i9kAWz+F93vBspegMNNOrRANYmNp2fl+U+CyJ9T7y16CkurX9bKLE+vBWATBLaBZJ8de62KBEfBoAtz5V8NeVwghbFRkMJGSpX65O7htU0xmhed+34PZXLeCSoqisEzmEwnh9qwaNKsotb9BrFq1qtK2G2+8kRtvvNGaS7ksS1JkWa+oqnlFh9JzuXfBNkpMZsZ2jeTFCV1JLX3DPZSeW+1xZVr0gUm/q+vMLH8ZTm2Fde/Ctnkw5FEY+ECDTJwX9ZCyQ01OtF4w4D7wbwrb50PWCVj/Poz4j+OunVg6XLXdSHUdoYam88z5NUIIz3TsbD6KAsG+XrxzU09GvbOaHclZ/LDtJLcMaFXr8ftSc0jNLsJPr2NIu/AGiFgI4QhW9RQJaNssoGxeUUJyVqXnU7MKmfzFFnKLjPRrHcr7/+qNTquhZagfTfz0GEwKh9PzKp+4KnFD4e54+Nd3ENEFirLVJOn9XrDlUzCW2LVtwo42fqj+7HY9NGkBel8Y84q6bf37kHXScdd2xnwiIYRwU0ml84naNAskuokfj4/uAMAb/xzkXF5xTYcCFxZsvax9OL76uhVgEkK4HkmKrFRxXtH5Cs9lFxqY/MUW0rKLaBcRyGd39it7g9RoNHRroZYTr3EIXeULQqcr4YF1cO0nENIa8k/Doifhg75odv8ASgNN3hd1k3US9v2m3h809cL2zldD6yHq0LZlLznm2pkn4Oxh0OigzXDHXEMIITyIZT5R22aBAEweHEvn6GCyCw288c/BWo8vm08kQ+eEcGuSFNmgqnlFRQYT9y3YxuGMPCKDffjyrgGE+FdcjLVbiyZAFRXo6kKrg543w7RtMP4dCIyErGS8/prKiIPPojm0COowvFE0gC1zQTGpxQaie17YrtHAFbMAjVqmO3mz/a99tHToXMxA8G1i//MLIYSHSTpr6SlSh6V76bS8OlGd9/zT9lNsPX6+2mPTsovYl5qDRgMjO0U4PlghhMNIUmQDS1K0IzmTIoMJs1nhiR93sfnYeYJ8vJg/ZQAtQvwqHde9NCmqsgJdXXl5Q/974JEEGPUSim8IwUUpeP08CT4bCUmrbD+3qL+iHNj+pXp/8MOVn4/uCb1vV+8vfsb+vXzl5xMJIYSo1dGynqILc3X7tg7llgExADz3214Mpqrfq5cfPK3u3yqUpoGV11cUQrgPSYps0LZZAM2CSucVnczilYX7WbgnDb1Ow9w7+tI5OrjK47o1V5Oig2m51b7B1pm3P1z6OMap2zkUeTWK3l8t+bzgGvjyajhzqH7nF7bZ+TUU50DT9tBudNX7jHwBvIMgdQcae5boNpZcSIplPpEQQtRKUZQKc4rKe/qKToQFeHMoI5d5645Vefzyg7JgqxCeQpIiG6jzitTeoud/38sX648D8M5NvRhcQ+WZ1k39CfL1osRk5nBGrn2C8W3CweY3YHxoGwy4H7R6OLYavr4ejLVPEBV2ZDLCpo/V+4OmgraaX6/ACBiqlujWrXwFnanIPtc/uRlK8tT1gaJ62OecQgjhwc7kFpNXbESrUf9Glxfi782M0uU03lt2pKxst0WRETYfU4fWSSluF3fuqPqF8c93od38MaF5R9Q1/UTtSgrU6rlfXg07Fjg7GoeSpMhGlmILR06r3e7Pje/M1T2b13iMRqMp6y2q1xC6qgRGwJX/Bw9vh6DmkH1S/U8sGs6BPyE7WS2/3fNfNe97yUMQGosmL532GX/b5/qWqnNtR1afkAkhhChztLSXKCbMv8qlMm7o25IBsWEUGkzM/HNfhecOZGkwmBTahAeUFWkQLkhR4M+H1S+M9/6CbtnzDD3yCl5vx8HcofD3dEj4Vh1hY5bCVWUyj8PS52B2Z/jrUfXf78+H1RExHko+OdnI0lMEcPelcdxzWZs6HVdWgS7FutWy6yy0NQx9Ur2/5i3HLxQqVIpyYbHW/veAvvKcsgq8fGC0WqK73el/1CS2vsrmE8nQOSGEqIuks+oXm23Cq177T6PR8Oq13fDSali6P4PlpeW3AfZkquvASS+Ri9v1nbpuoN4fhj2Duf0VFHk1QWM2Qtou2PY5/P4gfDgA3oxVpyEsfxkOLoLcjFpP71EURR2G/90t6vIvG/4HRVkQGgudrlL3+fNh2P+H82J0IKsWbxUXtAkP4JHL26EAj4/qUOfj6lWBrq5631G6Fs4J2DwXLpvuuGsJ1cnN6pwunY+aFNVF5wmYWw9Bd2I9mhUz4aYvbb9+Thpk7AE00PZy288jhBCNyNHTVc8nKq9DZBB3XxbH3NVJvPjnPga3DUcxm9lfmhTJfCIXVnAelj6v3h/2FFz6OCaDgSULF3LlpT3QZ+yCU9vUBddTd0JxtpoUlC9a1SQGWvRVby37qQWTvKtOot1WcR7s/l5dA/NMuTL0bS9Xp2a0Hw0aLfz1iDqE7ue74dYAj/sSVpIiG2k0GqaP6Wj1cZYKdAfScjCazHjpHNBZ5+UNw2fA7w+oyVG/u8AvxP7XERds+J/6s8dN6lDGutBoMI16Fc3nl6Pd/zuc2AitB9l2/aMr1J8t+kBA05r3FUIIAZTrKWpW84fcR0e25+9daZzKLOR/K44wKC6UQpOGUH89fVqFNkSowhbLX4aCs9CsE1xSbt1AjUZNdsLbQNdr1W0mI5zer37BmVKaKJ0+oI7kyD4J+38vPVYHEV3Uv7dxQ9XEwT+swZtmF+eOwtbPYOc3akII4B0IPW+BAfdBs4u+9L/qPSjOVddi/P52mPQ7tLqkoaN2GEmKGlhs0wACfbzIKzaSeCaPTlFVV6qrtx43wbp34ewh2PghXP6sY64j4HwSHFyo3h80zbpjo7pzoukwYs+tUkt037vStvlAlvlEHvatjRBCOJKl8lxtc4L8vb14cUIX7vtqO5+uTWJfqvrl04iOzdBpNQ6PU9jg1LYLc6vHv6N+YVwTnRdE91Bv/aao24pz1R6klO2lPUrbIbd0ZEbGHtjxpdqD0qKf+ve3/SiI7u3a83rNZkhaAZs/gSNLgdI1LsPaqolQr1vBt5rPplodXPuJ2rOUGA/f3AiT/664JqMbk6SogWm1Gro0D2bLsfPsOZXtuKRIq4MR/4Gf7oRNH8HA+yGg+sp4oh42fQwoagnuiE5WH34w+npa525Dk5agjn3ufZt1JzAZL/QUSVIkhBB1UmQwcSqzAKi9pwhgTNcoRnWOYNmB06w+fBaAUbJgq2syGeHvxwFF7fWIvdS28/gEqb1BcUMvbMtJVROkk5vVv72n98OpLept1evgH66uFdhulFr4yFVGbxTlqJ8xtnwC5xIvbG83GgY+oPZ41SWZ8/KGmxaoVY6TN8BX18FdiyG8veNibyAunMp6Lrss4loXna9WSzOX5Km9RsL+CjMvVGIZNLXmfatRrG+C+VK1RDfLZ6rfwFgjdYc6EdI3BJr3sSkGIYRobE6cK8CsQJCPF83quPDqixO64qtXPzrpNQpD2rnpsClPt+1zSN8Nvk3KihrZTXBz6HI1jH0NHtoIj++DCe9D5wnqGoQFZ2H3D/DrvfBWW/j0clg5C05uBbPJvrHUxdlEWPQUzO4C/zylJkQ+wTDwQXh4B9z+s9rDZU3vlrc/3PoDRPdS27tgImTZoWCUk0lS5ARlSVGqgyrQWWi1cHnpBMOtn6mT8YV9bfsCDAUQ2Q3aDLf5NOb+90FoHORlwLrZ1h1cVop7hNr9L4QQolZJZy7MJ9Jo6jYELibMn0dHqvMsuoQq+HvLe67LyU2HFa+q90e+CIHNHHu9Ji2h72S4+Wt4+hhMXghDHlM/F6CoQ+5WvwGfj4K32qlFCnZ9D3mn7RuHokBxLn7FZ9Sqevv/UHtxPugLW+ZCSS6Ed4Ar34bp+2HcG9C0re3X8w2G23+F8I6Qc0qt2mfvNjUw+W12AktZ7v2pOZjMimPHI7cfDTED1W7eNW/BVVZ+4BbVM5ao3dCg9hLV8Y9qlbx8YMyr8MNtsOED6HOnWl69LmQ+kRBCWC3pbN3mE13sgWFt6BjpT/q+LY4IS9TXkv9AcY5aLa7v5Ia9tk6vDtWLvRRGz1S/jE5cps6/OboKCs/D3p/VG6g9Le1Hq0PYWvRVv9hUFDX+wkz1VnD+wv3CrHL3y29Xb3qzkTEA+8sHpYEOV8DA+6DNiPp9VrlYQFO12MK8sXD+qJqETf4L/Nyz+IgkRU4QFx6Iv7eOghITR8/k0SEyyHEX02hg5Aswf7w6IXDII2q9eVF/+35VJ1wGRkG3G+p/vk7jIfYyOL4W4l+oW4nu/HNqhRyQpEgIIaxwtFxPkTU0Gg2XtQtn0WFHRCXq5ehK2PuLWvxg/Gx1frUzBUdDnzvUm8kIp7aqCdKReHV4X1qCelvzljqkTadXEx/F9mF2Jo0ebWA4Gr8wdQRJ/3sgLM5eLaosuDnc8Tt8MU4tPvHNTXDHb+DjfgsaS1LkBDqthq7Ng9l6PJO9KdmOTYpA/caizQhIWgmr3oRrP3bs9RoDRVF7dED99qW2qjZ1odHAFW/A3MvU0p8nNkDrwTUfk7QSUCCyOwRF1T8GIYRoJCyV52pao0i4EWMxLCpdvL7/vdC8l1PDqUTnpS670XqQ+mV1bgYcXa4mSEdXqHODy/PyU3tc/ELVkt9+IRce+4WVu295PhSDVyCL4ldy5ZVXotfrG65tTduqidAXV6oFJ364DW79UR0F40YkKXKSrs2bsPV4JntSsrmuT0vHX/Dy59UP0Lu/h0sfg2bWr7Ekyjm2Rv1GRO8PfafY77xR3aDPJLWM6OJn4N5VNU9+LBs6N9J+MQghhIdTFMXmniLhotb/Vy0iEBjpHsuQBEWq5a973XphjSStrjThCQG9n/XnNBjsHmadRXaF23+BL69WF7/9+S648Uu3musshRacpMEq0Fm07Asdx4NihpWvN8w1PdnG0l6iXrfZf9G2Ec+p3ehpu2DXt9XvZzbLfCIhhLDB2bwScouMaDTq+oHCzZ0/BmvfVu+PfV2tOudOLGskRXZVh9zZkhC5gpb94JbvQOcDB/+GPx9WP6u4CUmKnKR7S/UXdl9psYUGcfmzgEYdmpW2q2Gu6YnOHCpd8EwDlzxo//MHNoOh/1bvL39ZXTyuKum7If+Muvp0zED7xyGEm/nwww+JjY3F19eXgQMHsmVLzRPhs7KymDp1KtHR0fj4+NChQwcWLVrUQNEKZ7JUnmsZ6oev3snzTkT9KIpaatpYBHHDoNv1zo6ocWszDG78AjQ69Yvdxc+or5EbkKTISdqEB+Cr11JQYuJYaQUch4vseuHNYsVrDXNNT7TxQ/Vnp/H1K2dZk4EPQFgbtUT32neq3sfSSxQ3zD5zmoRwYz/88APTp0/nxRdfZMeOHfTs2ZOxY8dy+nTVJWJLSkoYPXo0x48f5+eff+bQoUN8+umntGjRooEjF85gqTzXJlzmE7m9A3+pX1TqvGH8O/atriZs02k8TCydv75lLqya5dx46kiSIifx0mnpEq2W5m6wIXQAI/6jZu9HlsBJKSdqtbwz6voCAIOmOe46Xt5qiW5Qk7DM45X3SVyu/mwvQ+eEmD17Nvfeey9TpkyhS5cuzJkzB39/f+bNm1fl/vPmzeP8+fP8/vvvDBkyhNjYWIYNG0bPnj0bOHLhDEdPy3wij1Ccp/ZEAAx5FMLbOzcecUHPm9U1kQBWv3mhOJULc5/ZTx6oe4sm7EjOYk9KNhN7N9C3k03bqpP6dn6lDs2a/HfDXNdTbP0MTMXQvA+0usSx1+p4pdoLdGw1LH0ebv7qwnNF2eraUwBtpciCaNxKSkrYvn07M2bMKNum1WoZNWoUGzdurPKYP//8k0GDBjF16lT++OMPmjVrxq233srTTz+NTld5OFVxcTHFxcVlj3Ny1MW3DQYDBhsmN1uOseVYd+Dq7Us8rQ5Lbh3mJ6/fRdypbdqVr6PLSUEJaY3xkkfqVGjAndpnLZdrW+/JaAsy0a16DZY+i1EfgNLrdptPV759jmijJEVO1LWhiy1YDHsadv+groeTtAraDG/Y67srQ6GaFAEMnub4LnqNBq6YBXMuhQN/wvF1anl1gKTV6joG4R3qvsirEB7q7NmzmEwmIiMjK2yPjIzk4MGDVR6TlJTEihUruO2221i0aBGJiYk89NBDGAwGXnzxxUr7z5o1i5kzZ1bavnTpUvz9/W2OPT4+3uZj3YGrtm9vsg7QcPboXhad3WPzeVy1ffbg6m0LKjzJ8IPqEK1NYTdwOn6lVce7evvqw6XapnSgS8R42p9eiG7h42zbd4TU0PrNg46Pj6egoMBOAV4gSZETWSrQ7UvNwWxW0GobaBxsSIxaRnrLXFj+itob0VBjcBVF/XAfFA3h7Rrmmvay+wcoOAtNYqDzNQ1zzciu6orc2+apQwTuW62W7JSqc0LUi9lsJiIigk8++QSdTkffvn1JSUnhrbfeqjIpmjFjBtOnTy97nJOTQ0xMDGPGjCE4ONjq6xsMBuLj4xk9enTDrifSQFy5fcVGM9M3LwcU/jV+BJHBvlafw5XbV19u0TbFjG7BBLSYMXe8in43zKj9mFJu0T4buWzblCsx/fMEup0L6Jf8CaaBl6HY8PmlfPsKCwvtHqYkRU7UPiIQHy8tecVGjp/Lb9gF5C57AnYsgJRtcHgxdBzn+GuazRD/vFrOWqNVh/EN/w80cYOJzWYzbPxIvT/wgYatuz/iWdjzC6TvgYRvoPcdsj6REOWEh4ej0+nIyMiosD0jI4OoqKoXNY6Ojkav11cYKte5c2fS09MpKSnB27ti8RIfHx98fCovRKjX6+v14aO+x7s6V2zf8fO5mMwKAd46WoQFoqnHl4Ku2D57cem27fwaTm0GfQDaK99Ea0OcLt2+enLJtk14Dwz5aPb+gtcvU+COX2tfoL4aer0eo9Fo3/iQQgtO5aXT0tlSbCE1p2EvHhQJA+9X76941fF15E1G+GPqhfV9FLP6pva/PhD/AhRmOvb69ZW4DM4eAu8gdXHVhhQQDsOeUu8vfxlObYWcFPDyhdZDGjYWIVyQt7c3ffv2Zfny5WXbzGYzy5cvZ9CgQVUeM2TIEBITEzGXe+87fPgw0dHRlRIi4VmOnimtPNesfgmRcJKC8+o8W4Dhz0CTls6NR9SNVgfXzoX2Y8FYCN/eDKkJzo6qAkmKnKxbCydUoLMY8qi6SGjGXtj3q+OuYyiEH+9Q69VrdGqZxrvjodVgdV2B9e/D+z1h3Xvqvq5o4//Un33vBF/rh8rU24D7IKytui7RT5PVbbGXuu8Cb0LY2fTp0/n000/58ssvOXDgAA8++CD5+flMmTIFgEmTJlUoxPDggw9y/vx5Hn30UQ4fPszChQt5/fXXmTp1qrOaIBpI0lm18lxbqTznnpa9BIXnIaKLY9YKFI6j08NNX6pf6BbnwHf/cqnPfZIUOZllXtGeU05IivzDLpSVXjVL7c2xt6Js+PoGOLRIXeH45q/VYXMxA2DKIrj1R/WNrSgblr0I/+2jDutzRCy2StsNx9aoCd3AB5wTg5c3jC1dWyonRf3ZbrRzYhHCBd188828/fbbvPDCC/Tq1YuEhAQWL15cVnwhOTmZtLS0sv1jYmJYsmQJW7dupUePHjzyyCM8+uijPPPMM85qgmggSeV6ioSbObkFdnyp3h8/W/2QLdyL3g9u+V5NjK7+wKW+3JU5RU7WzVKBLjUbRVEaviv/kgdh8xw4lwi7voM+d9jv3Hmn4evr1LkwPsFwy3cXqqeBWtyhw1i1WMDuH2Hla5B9Ev58WK1nP+pFaOMCH/wti7V2nagWqXCWDleolQKTVqmPpciCEBVMmzaNadOqXj9s1apVlbYNGjSITZs2OTgq4WqSzsgaRW7JZIS/S4ud9LodWlc9NFa4Ad9gmLzQ5RbalZ4iJ2sfEYS3TktukZHk8/YvL1gr32C49HH1/uo3wVhc8/51lXkC5o1VE6KAZup6SOUTovK0Ouh1C0zbBmNeA79Qdf7O97eiWzCesLxD9onJFjmpsPdn9f4gJw+r0Whg7CzQ+0NUD3XNKSGEEHWmKMqFOUXh0lPkVrZ8Ahl7wDcERlcujy/cjIslRCBJkdN5e2npFB0EwB4b5hV9seEEr+zQcTgj1/YgBtwLgVFqL832L20/j0XGfjUhOp8ETVrBXUsgug6rxOt91fV/Ht2lVsfz8kN7aguXHXkN3Y+3qedtaFs+AbNRnf/Uom/DX/9ikV3g4R0u+Q2LEEK4uvP5JWQXqos+xoVLT5HbyElVR5OAmhAFhDs3HuGRJClyAWVD6FKsq0B3/Gw+by09zNliDW/HH7E9AL0fDH1Svb/2bSipR4/VyS3wxTjITYNmneHuJdb3aPg2gZEvwCM7MfW+EzNatEeWwJwh8PtUyD5le3zWKM5T1wcC5/cSlRcc7ZxiD0II4eaSzqq9RC1C/PDz1tWyt3AZS/4DJXnQsj/0buAKsKLRkKTIBXRrbkmKrOspem3RAQwmBYCVh87Wr4JdnzshpBXkZai9I7Y4sgwWXANFWeob15RFENzc9piCozFf+Q4rO8/C3GmCWsY74Wu1GMPS59SynI6U8K1aACKsTcOs4ySEEMKhZD6Rg5hNcHgppO2yf6GkxOWw7zd1fcPxs0ErH12FY8j/LBdQVoEuRS22UBfrjpwlfn8GOq2GtkHqMf9bUY/eIi9vGFZadWn9e2oyYI09P8N3N4OhQC0AMOkPtbqdHeT5RmO6/gu4Zzm0vhRMxbDhf/B+L1g7u349W9Uxm2BTaYGFSx5S5z0JIYRwa5b5RG2l8px9/fM0fHsjzB0Ks1rCvCtgybOw91d1jnEdP9tUYiiCRaUjWQY+ANE97BezEBeR6nMuoENUIHqdhuxCA6cyC4kJ869xf6PJzMt/7wPgtgExtChM4o3dXizZl8HB9Bw6Rdk4tKrHzbDuXTh3BDZ+BCNm1H4MwJZPYdG/AQW6XQ8T56hJlr217KcWbEhcpq5TkLEXls9Ue7Z63QaRXdXy3k3bga6e/7UPLoTM4+qEzl632iF4IYQQziY9RQ6w63vY+ql636cJFGdD8kb1ZhHQTJ2X26IftOwLzfuAX0jt517/njo/OSgahtfxM4kQNpKkyAX4eOnoGBXE3pQc9qZk15oUfbclmcMZeYT663nk8rasX5nEFV0i+WdfBv9bkciHt/axLRCdF4z4D/w8RS1DPfD+mnt7FAVW/x+sel193P8eGPeWY7u2NRpoPxrajoQ9P8GKVyE7WZ0LVdYObwjvqBYliCi9RXaB4BZ1L05gKcPd/27wlj+eQgjhKPtTc0g6m8f47tEOX5YiSXqK7Ct9D/z1mHp/2DMw7Gl1iY+U7ZCyDU5tU7/AzD8DhxerN4um7dVEqWU/9Wdkt4pfqJ47qo4GARj7usylFQ4nSZGL6Na8CXtTctiTks247tHV7pdVUMI78YcBmD66A0381IXLHhrehn/2ZbBoTxqJp3NpFxFkWyBdJkLkbLXs5bp3YcwrVe9nNsPiZ2DLXPXxsKfVb3EaqiKaVgs9b1bXDtr9I5zaolanO30ADPlq/Bl7Kh7j26Q0Sepcmih1Ve/7hVbc79Q2OLkJtHrof2/DtEcIIRqpad/tIOlMPt53aBnTNcph1zGYzGVLX0hPkR0UZsIPt4OxUF1MfNjT6t/mZh3UW69b1P0MRZC+W02UTm1Tf2YeU0elnDsCu79X99P5qMPjLD1KCV+rw+XbXg5dr3VeO0WjIUmRi+jWoglsPcne1Jor0L237AhZBQY6RgZxy4BWKGYTAJ2ighjTJZKl+zP4YEUi7/2rt22BaLVw+XPq/KAtn6pV14Iu+iNlMsDvD6o9NQDj/k/tVXIGLx91wVnLorNmM2SdUJOj0/tKE6X9cPaIOk/q4i59gKDmF3qVIrvC3l/U7d1vVCu9CSGEcIjsQkNZ783cNUkOTYpOnCvAaFbw99YRFezrsOs0CmYz/PaAOsw8pBVc90n1o0T0vhAzQL1Z5J8r7U0q7VFK2a4mWae2qjcLnQ9c+bYsQSEahCRFLqJ7iwsV6BRFqXIIwZGMXL7adAKAFyZ0wUunxVCaFAE8MrI9S/dn8OeuVB4d1cH2NRg6jFWrx53aCmvehvHlhqaVFMCPkyAxHrReMPFj6HGTbddxBK0WwuLUW6crL2w3FquJ0en9kLGv9Od+yDkFuanqLXFZxXO5UhluIYTwQAfTLnwRuP1EJtuOn6dfrH2K9FzMMp8oLjzA4cP0PN7ad9ShcF6+cPPX1hdWCmgKHcaoN1CH459PupAondqm/s0e/owsVC4ajCRFLqJjVBBeWg3n80tIzS6iRYhfhecVReGVhQcwmRVGd4lkSLvKC5d1a9GEyztFsOLgaT5cmcjbN9ZhwdSqaDRw+fOw4GrYPh8GPwyhrdVvcb69GU5uBi8/uGnBhTc0V+flA1Hd1Ft5hVlw5mDFROnsYeg0vvK+Qggh7Gp/WsXREXPXJDkuKSpdo6iNzCeqnyPLLiykOn523RZnr41GoyY/Tdu61hetolGRktwuwlevo32kOg+oqvWGVh46zZrDZ9DrNDx7Zedqz/Pw5e0A+G1nCsnn6lGqus0wiBsKZoNaTCE3Hb64Uk2IfJvApN/dJyGqiV8ItLpELagw/h246x946ihc/V9nRyaEEB7vQGlSdFWPaDQaiN+fQeLpPIdcy9JT1FbmE9ku8zj8cjegQN8p0Ps2Z0ckhN1IUuRCurdQK6tcnBSVGM288vcBAO66NI7YGobF9W4VymXtwzGZFT5enVi/gC5/Qf2561v4bJTakxIYBVP+URMJIYQQoh4OpOUCML57NKM7RwLw6Zokh1zLMnfJ7XqKFEVd72f+VeqagM5iKFSHzxdlqSW1x73pvFiEcABJilxIt3KLuJa3YONxjp3NJzzQh2kj2tV6nkdHtgfg5+2nSMkqtD2gmP7QYRwoZsg+CaFxcNditRiBEEIIUQ9Gk5lDGWpS1Dk6mPuHqXNHftuZwumcIrtf76hljSJb59s6Q3YKfHeLulTG8bVqL83K121fDNVWigILn4S0XeDfVB0+7+XTsDEI4WCSFLmQbhcVWwA4m1fM+8uPAPDvsR0I8tXXep5+sWEMbtsUg0lhzqqj9Qtq5AvqcLnoXnDXErWAgRBCCFFPx87mU2I0E+Cto1WYP31bh9KvdSglJjNfbDhu12tl5peQWWAA3KQct9msVoD9cCAc/kddIqJ96ZD11W/CL/eopa4byo4v1RLZGi3cMA9CYhru2kI0EKuTojVr1jBhwgSaN2+ORqPh999/r3H/VatWodFoKt3S09NtjdljdYkORqfVcDavhIycYgDeWXqY3CIj3VoEc0Pfur8JPXy52lv0w9aTpGfX440zsgtMPwD3rYKgSNvPI4QQQpRjKbLQMSoIrVatBmfpLfp60wnyio12u1bSWbWXqHkTX/y9XbzG1OmD8MUVsOhJKMlVq8E+sBZu+wkm/Fet/Lr3Z1hwDeSfdXw8Kdth0b/V+5c/D22GO/6aQjiB1UlRfn4+PXv25MMPP7TquEOHDpGWllZ2i4iIsPbSHs9Xr6N9hDrWeU9KNvtTc/hhazIAL1zVFZ227iVEL2kTxoDYMEpMZuasrmdvkXeArBEghBDCrizziTpHB5dtG9kpgrbNAsgtMvL9lmS7XeuoO8wnMhbDqjdgzqVqUSPvQBj3ljpKI6K0wFLfO+H2X8CnibrI+Gcj4cxhx8WUfw5+mASmEuh0FVz6uOOuJYSTWZ0UjRs3jldffZVrr7VudeGIiAiioqLKbtrqFvlq5Lo2vzCv6OW/92FWYHyPaAbEWVeiVKPR8PBIdf7Rd1uSOZ3bgN3sQgghRC0slefKJ0VarYb7hrYB4PN1xygxmu1yrbL5RK46dC55M8wdCqtmqVVfO1wBUzfDwPtAq6u4b5vhcE88hLRWq8F9PgqSVts/JrMJfrlLXc8vrC1M/Ei+IBUercH6kHv16kVxcTHdunXjpZdeYsiQIdXuW1xcTHFxcdnjnBz1jdNgMGAwGKy+tuUYW45taF2iA/kF+GrjcTILDPh4afn36HbVxl5T2wa2bkKvmCYknMxm7qpEnrmioyNDdwh3eu1s4cnt8+S2gWe3r3zbPLF9wjVUlRQBTOzdgreXHiYtu4i/dqVyfd+W9b5WWeU5VyuyUJQDy1+GrZ8BCgQ0U6u6db2u5gSkWUe4dwV8f6vaq/T1dXDVe9DnDruFpl39BiStAr2/ukCrbxO7nVsIV+TwpCg6Opo5c+bQr18/iouL+eyzzxg+fDibN2+mT58+VR4za9YsZs6cWWn70qVL8ff3tzmW+Ph4m49tKLm5AF5lE0KHRxrZtWElu2o5rrq2DQzQkICOrzYep03xUQJrr9PgktzhtasPT26fJ7cNPLt98fHxFBTUY70zIapxLq+Y07nFaDTQKSqownM+XjruGhLHm4sP8smaJK7r0wJNPXsoksp6ilxo+NyhxbBwOuSkqI973Q5jXgH/Oo4MCQiHSX/CHw/B3l/gz2lwLhFGvgj1HI0TlbUd3c731QdX/0+dXyyEh3N4UtSxY0c6drzQQzF48GCOHj3Ku+++y1dffVXlMTNmzGD69Ollj3NycoiJiWHMmDEEBwdXeUxNDAYD8fHxjB49Gr3etbOCghIj/923ArMCkcE+/N9dQ2qcFFpb28YpChvmbmZPSg7Jfu15ckx7R4Zvd+702tnCk9vnyW0Dz25f+bYVFtajrL8Q1bDMJ2od5k+AT+W/cbcObMUHK45wKCOXVYfPMKKj7fOQDSYzyefV5L5thAskRXmn4Z+nYd+v6uPQWLWXp+0I68+l94XrP4em7dSqdOvfg/NJcO1c8LbxS+TzR+lz4hP1/sAHofsNtp1HCDfjlBIsAwYMYN26ddU+7+Pjg49P5fr3er2+Xh8+6nt8Q2ii19OtRRN2n8pmxrjONAnwq9NxNbXtkZEduHfBNr7enMyDI9oR4u9tz5AbhDu8dvXhye3z5LaBZ7dPr9djNNqvApgQFvvT1PX4Lh46Z9HET8+tA1vx6dpjzF19tF5J0cnzBRhMCr56LdHBvjafp94UBRK+gSXPqgugarQwaBoMn2F7AgPqMLsR/4GwNvDnw3DgT8g+Bbd8b33V2JJ8vH6ejMZciLnlQLRjXrE9LiHcjFOqHSQkJBAdHe2MS7uFD27pwxdT+nNNr+Z2Od+ozhF0jg4mv8TEvHXH7HJOIYQQwlZVVZ672F2XxuGl1bAp6Ty7TmbZfC3LfKK48MCy0t8N7nwSLLga/piqJkRRPeDelepwufokROX1/BdM+gP8wiB1h1qZLmNf3Y9XFPjzETRnDlDk1QTTdZ+DzjO/8BGiKlYnRXl5eSQkJJCQkADAsWPHSEhIIDlZLZ05Y8YMJk2aVLb/e++9xx9//EFiYiJ79+7lscceY8WKFUydOtU+LfBArZr6M6JjRL3HUFtoNBoeuVytRPfF+uNkF8rEaSGEEM5TXZGF8qKb+HF16ZeDn6xJsvlaljWKnFJ5zmSEde/BR4Pg2Brw8oXRL6sJUfNe9r9e68FwzzJ1OF32Sfh8LBxZVrdjN8+FvT+jaL3YGjcNgqLsH58QLszqpGjbtm307t2b3r17AzB9+nR69+7NCy+8AEBaWlpZggRQUlLCE088Qffu3Rk2bBi7du1i2bJljBw50k5NEHUxtmsUHSIDyS02ct+CbXYv0X0urxizWbHrOYUQQnieYqOJxNNqotKlec3zhC3luf/Zm8bxs/k2Xc/SU9S2oYsspCbApyNg2YtgLIK4YfDQRhjyKOgcOHuhaVu4Ox5iL1MXf/32Rtjyac3HnNgIS58FwDxyJucD3a9arRD1ZXVSNHz4cBRFqXSbP38+APPnz2fVqlVl+z/11FMkJiZSWFjIuXPnWLlyJSNG2DCZUNSLVqth5tXdCPDWsfnYecb/dx2bk87V+7x5xUae/W0PfV9dxnN/7LVDpEIIITxZ4uk8jGaFYF8vmjepeY5Pp6hgRnRshlmBz9bZ1lt0ISlqoJ4isxnWvgOfXg7pu8E3BK75SB3aFtamYWLwD4Pbf4Wet4JihkVPwuIZ6tpDF8tNh5/uBLMRul2Puf99DROjEC5GVlBtRAa1bcof0y6lQ2QgZ3KLufWzzcxZfRRFsa2HZ33iWca+u4ZvNqs9g8v2Z9h8LiGEEI1D+flEdRkmft/QtgD8tO0UZ/OKa9m7srKFW8MboKeoKBt+uF1de0gxQddrYdpW6H1bwy986uWtLrh6+fPq400fwfe3QXHehX1MBvhpCuRlQLPOMOG/skCraLQkKWpk2kUE8vvUIVzbuwUms8Ib/xzk3gXbrZpnlFds5D+/7eG2zzaTklVIy1A/vLQaTucWk5pt32F5QgghPEtd5hOVd0mbMHq2bEKx0cyCjSesulZ2gYFz+SUAxDm6pyhjP3wyHA4tBJ2PmmDcOB8Cba+cV28aDQx9Em74Qo3p8D/wxRWQXbo2UvyLkLwBvIPUBVp9XKBkuRBOIklRI+Tv7cXsm3ry+rXd8dZpWXYgg6v+t5a9Kdm1HrvuiNo79G1p79Adl7RmyWNDy/647TiR6dDYhRBCuDdLUtSljkmRRqPh/mFqb9GCjccpKKl7mfijpUUWooJ9CaxiPSS72fOzWu3tfBI0iYG7FkPfOx13PWt1uw4mLwT/cEjfo8a66k3Y9KH6/LUfQ3g758YohJNJUtRIaTQabh3Yil8eHEzLUD9Oni/kuo838N2W5CqHwOUWGZjx6x5u/1ztHYoJ8+PbewfyysRuBPh40adVCAA7kiUpEkIIUTVFUazuKQK1WFDrpv5kFRj4cevJOh9nmU/ksMpzJgP88wz8cjcYCqDNCLhvNbTo45jr1UdMf7h3OTTrBLlpsOp1dfulj0PnCc6NTQgXIElRI9e9ZRMWPnwZIztFUGI0M+PXPTzx0y4KSy5Mxlx75AxXvLeW77aovUN3DmrN4keHMrhteNk+vVuFArAzOatB4xdCCOE+MnKKySwwoNNqaB9Z96FaOq2Gey5TixR8tu4YRpO5TseVzSdyQFLkY8hC9/VE2PyxuuGyJ+H2XyCgqd2vZTehsXD3UjV5A7Ui3ojnnBqSEK7CgX3Jwl008dfz6aR+zF2TxFtLDvLrjhT2peTw1o09+G5LMt9tUb+VaxXmz5vX92BQ28pv+H1Kk6J9qdkUGUz46nUN2gYhhBCuz9JL1CY8wOq/Ezf2bcl78Yc5lVnI1R+s55lxnRjaoVmNxyQ5qMiCJnkjww8+j9aYDT7BcO1c6HSlXa/hML5N4Laf4dRWtUfLkeXBhXAj0lMkALVk94PD2/LNPZcQHujDoYxcrv5gfVlCNHlwLIsfu6zKhAggJsyPpgHeGEwK+1JzGjJ0IYQQbmK/DUPnLHz1Ol67thtBPl7sT8th0rwt3P7Z5hrnw9p9+JyiwMaP0H09EV9jNkqzznDfKvdJiCx0XtB6EHj5ODsSIVyGJEWigkFtm7LokUsZGBcGqL1D3993CS9d3RV/7+q/TdJoNOWG0Mm8IiGEEJXVJykCuKJbNKufGsFdQ+LQ6zSsSzzLVf9bx6Pf7+Tk+YIK+xpNZk6cU7fZZeHW4jx17tCSGWgUEydDB2GcvFhdLFUI4fakz1RUEhHsyzf3DCThZBZdmzfBz7tuQxz6tA5h2YEMmVckhBCiSheKLATZfI6wAG9emNCFKUNieXvpIf5ISOWPhFT+2ZPOHYNaM21EO0IDvDmVWUiJyYyPl5YWIX71C/xsorr+0JkDoPXCNOoVdpxuzpXeDbQgrBDC4aSnSFTJS6elX2xYnRMigN4xak+RVKATQghxscISE8fPqsPZ6lqOuyYxYf68/6/e/P3wpQxp15QSk5nP1x1j6Fsr+XjV0bJeqbjwALTaeixIeuBvdf2hMwcgMAomL8Tc/15Z5FQIDyM9RcJuesY0QauBtOwi0rILiW5Sz2/mhBBCuIyMnCK8tBqaBto2D+VQRi5mBZoGeNMsyH5zWbq1aMLXdw9kzZGzvPHPQQ6k5fDm4oN4lSZCNs8nMhlh5auw7l31cavB6mKsQZFgqPuC50II9yA9RcJu/L296BSlfvsnQ+iEEMJz5BYZGPveGq7+YD3FRlPtB1Sh/PpEGjv3smg0GoZ1aMbChy9l9k09aRHih9GsrrlnU+W5/LPw9XUXEqJLpsKdf6oJkRDCI0lSJOyqT+sQAHackCF0QgjhKY6eySerwEBKViErD5626RyWpKhL8/oPnauOVqvhuj4tWf7EMP5zZScuax/OdX1aWHeSU9th7jA4thr0AXDDPLjiddDpHRO0EMIlSFIk7Moyr2jnySznBiKEEMJukstVdvtlR4pN57BHkYW68tXruG9oW766eyBt6lp5TlFg2xfwxRWQcwqatoN7l0O36x0brBDCJcicImFXfVqrSdGelGxKjGa8vSTvFkIId1e+3PXKg6c5n19CWIB3nY9XFIWDabmA7eW4Hao4F/55BhK+Vh93ugomfqQudCqEaBTkE6uwq9im/oT66ykxmssq/wghhHBv5ZMio1nhr12pVh1/KrOQ3GIj3jqtfdYMspeiHFjzNrzXXU2INFoY9RLc/LUkREI0MpIUCbsqv4irzCsSQgjPcDJTTYq6t1AThV93nLLqeMuXZO0iAtHrXOCjR1EOrHkL3u8BK16Bwkx1uNwdv8Glj0u5bSEaIRd4ZxKepk+rEEDmFQkhhKewzCmaOqItXloNu05lk3g6t87H70+9UHnOqYqyYfVbas/QildLk6H2cN2nMHULtBnu3PiEEE4jc4qE3UlPkRBCeA6jyUxqVhEAvWJCGd6xGcsOnObXHSk8dUWnOp2jIYssVKkoGzbNgU0fqvcBwjvA0Keg23WgrftC5UIIzyQ9RcLuesaEoNFASlYhp3OKnB2OEEKIekjLLsJkVvD20hIR5MN1fVoC8NvOFEylawHV5kB6aTnuhu4pKsyCVW+oPUOrXlcTovCOcP3n8NAm6HGjJERCCEB6ioQDBPp40TEyiIPpuexIzuKKblHODkkIIYSNLEPnWob6odVquLxTBMG+XqRlF7Ep6RxD2oXXeHxukYGT5wuBBhw+V5gFmz5Wb8WlPUPNOsGwp6DLREmEhBCVSE+RcAjLELqdJ2UInRBCuDNLUtQqzB9Q1wC6qmdzAH6pQ8GFg+nq3KOoYF9CrSjjbZPCTFj5utoztPoNNSFq1hlu+AIe3KiuOSQJkRCiCpIUCYfobSm2cCLLqXEIIYSon5MXJUUA15cOoVu8N538YmONxzfIfKKC82rhhPd6wOo3oTgHIrrAjfPhwQ2l84bkI48QonoyfE44RJ/SnqLdKVkYTGbXKMEqhBDCapaeopjQC0lRn1YhxDb15/i5ApbsSy+bZ1SVC0mRA4bOFZyHjR/C5rlQUloNL6IrDH8aOk2QREgIUWfybiEcok14AE389BQZzGWrmAshhHA/JzPV+UAx5XqKNBpNWSL0646UGo/fX/o3wK5JUUk+LH9ZHSa39m01IYrsBjd9BQ+sgy7XSEIkhLCKvGMIh9BqNfSKCQFgR7LMKxJCCHdV1fA5gGt7twBg/dGzpGUXVnmsyaxwyFJ5rrmdkiJDIXx7M6x9B0ryILI73Pw13L8WulwtyZAQwibyziEcxjKEbqckRUII4Zbyio2czy8BICbMr8JzMWH+DIgLQ1HU8txVOXGugCKDGV+9ltimAfUPyGSAn6bA8bXgHaT2DN2/BjrLUDkhRP3IO4hwGEuxhR3JWU6NQwghhG0svUSh/nqCfPWVnr++j9pb9OuOFBSl8ppFlspzHaOC0Wk19QvGbILfHoDD/4CXL9z6g/QMCSHsRt5JhMP0aqUu4pp8voCzecXODkcIIYSVyoosXDR0zuLK7tH4eGlJPJ3HnpTsSs9bkqIu9a08pyiw6EnY+zNovdQeotgh9TunEEKUI0mRcJhgXz3tmgUCsFN6i4QQwu2crCUpCvLVM7arukB3VQUX9qfbqcjC8pmwbR6gges+gQ5j6nc+IYS4iCRFwqFkXpEQQriv6ooslHdd6RC6P3elUmI0V3juoD2SorWzYd276v0J76sLsAohhJ1JUiQc6sK8ItuSoqQz+ZyXkXdCCOEUVa1RdLFL24XTLMiH8/klrD58pmx7vgEyctQ38E5RNg6f2/q52ksEMOZV6HunbecRQohaSFIkHKpP69JFXE9lYzSZa9m7or0p2Vz14Qbe26vDZK48gVcIIYRjWdYoqqmnyEunZWKv5gD8sv1U2faUArWwQkyYX5VFGmq1+ydY+IR6f+i/YfDD1p9DCCHqSJIi4VDtmgUS5ONFQYmJQxl1X8TVaDIz49c9GEwK2SUaq44VQghRf2azUm5OkV+N+1oWcl1+MIOsArWEd0q++lznKBuGzh36B367H1BgwH0w4lnrzyGEEFaQpEg4lFaroZcNpbnnbzheoZLR9hN1P1YIIUT9nckrpthoRquB5iE1J0Wdo4PpHB2MwaTw1+40AFJLe4qsnk90bA38eCcoJujxL7jiTdDUs5y3EELUQpIi4XC9rSy2cPJ8Ae8sPQxAx0i1et22E1KoQQghGpKll6h5iB96Xe0fFy6sWaQOoUvJtyEpOrUdvrsFTMXQ6Sq45kNZh0gI0SDknUY4nKXYQl3KciuKwnO/76XQYGJAXBjPXdkJUHuKqloYUAghhGPUpchCeVf3ao5Oq2FnchZHMvJIV6cj0aWuSVHGfvjmeijJg7hhcP3noPOyJXQhhLCaJEXC4XrHhABw7Gw+mfklNe77565UVh8+g7eXllnXdadnyyboNAoZucWcPF/YANEKIYQAyt5zayqyUF5EkC9D24cDMHvZEUyKhgAfHS1Dax56B8D5JPjqWijMhBb94F/fgt7X5tiFEMJakhQJhwvx96ZNswAAdp6sfhhcZn4JL/+1H4CHR7SjbbNA/Lx1xKiHsvX4eYfHKoQQQmXpKWrVtG5JEVwouLDsoFqau1NkEFptLfOBclJhwUTIS4eIrnDbT+ATaFPMQghhK0mKRIO4sIhrVrX7vLboAOfyS+gQGcj9w9qWbW8TrA6b23ZCkiIhhGgoljlFderpKTW6SyRBPheGvHWOrmV9ovxzag9R1gkIjYM7fgP/MJviFUKI+pCkSDQIS1JU3SKu6xPP8vP2U2g0MOu6Hnh7Xfiv2SZITYq2HJOkSAhRvQ8//JDY2Fh8fX0ZOHAgW7ZsqdNx33//PRqNhokTJzo2QDdzMrO0p6iOw+cAfPU6xveILntc46KtRTnw9XVw5iAENYdJf0BQpM3xCiFEfUhSJBqEpdjCrpPZlRZiLTKY+M9vewC445LW9C1d8NXCkhQdPZPPubxixwcrhHA7P/zwA9OnT+fFF19kx44d9OzZk7Fjx3L69Okajzt+/DhPPvkkl112WQNF6h6KjSbSc4oAiLEiKQK4vm/LsvvVJkWGQvjuX5CWAP5N1YQotLWt4QohRL1JUiQaRIfIIAK8deQVGzlyuuJCrO8vP8KJcwVEBfvy77EdKx0boId2pXOSpDS3EKIqs2fP5t5772XKlCl06dKFOXPm4O/vz7x586o9xmQycdtttzFz5kzatGnTgNG6vpTMQhQF/L11NA3wturYfq1DubRdU2IClKqTImMJ/DgJTqwHn2C4/Vdo1sFOkQshhG2k1qVoEDqthp4xIWw4eo6dyVl0Kl3hfH9qDp+sSQLg5Wu6EuSrr/L4frGhJJ7JZ9vx84ztGtVgcQshXF9JSQnbt29nxowZZdu0Wi2jRo1i48aN1R738ssvExERwd13383atWtrvEZxcTHFxRd6qnNycgAwGAwYDAarY7YcY8uxDeHYGfXLq5hQP4xGo9XHf3JrD+Lj49EqpoptNJvQ/XE/2iNLUbz8MN30DUqzruCi/w7VcfXXrz48uW3g2e3z5LZBxfY5oo2SFIkG06dVKBuOnmPHiUxuGdAKk1lhxq+7MZkVxnWLYkwNyU6/ViF8v/UUW45LT5EQoqKzZ89iMpmIjKw4HyUyMpKDBw9Wecy6dev4/PPPSUhIqNM1Zs2axcyZMyttX7p0Kf7+1g0vKy8+Pt7mYx1pbboG0KEvyWHRokU2n6dC+xSFnifnE3tuJWaNjs2tp3J6bybstf38zuaqr589eHLbwLPb58ltA7V9BQUFdj+vJEWiwVjmFVmKLXy54Ti7TmUT5OvFzKu71nisZZ7RvpRsCkqM+HvLf10hhG1yc3O54447+PTTTwkPD6/TMTNmzGD69Ollj3NycoiJiWHMmDEEB9dxcdJyDAYD8fHxjB49Gr2+6h5yZ9q9+BAcO0G/TrFcWbqItjUqtU9R0K6Yie7cShSNFvO1n9Cv8zUOiLxhuPrrVx+e3Dbw7PZ5ctugYvsKC+2/dqXVnyzXrFnDW2+9xfbt20lLS+O3336rtWLPqlWrmD59Ovv27SMmJobnnnuOyZMn2xiycFe9SyvQHT2Tz/7UHN5eegiAZ8Z1IiK45kX6WoT4Et3El7TsIhJOZjG4bd0+yAghPF94eDg6nY6MjIwK2zMyMoiKqtwDffToUY4fP86ECRPKtpnNZgC8vLw4dOgQbdu2rXCMj48PPj4+lc6l1+vr9eGjvsc7SkqWOlQwNjyw/u3z8oIVr8KmDwDQTHgfrx432CVOZ3PV188ePLlt4Nnt8+S2gdo+W4b11sbqQgv5+fn07NmTDz/8sE77Hzt2jPHjxzNixAgSEhJ47LHHuOeee1iyZInVwQr3FhbgTVy4WjDh7i+3UlBiYkBsGLf0b1XrsRqNhn6x6toVW4/JEDohxAXe3t707duX5cuXl20zm80sX76cQYMGVdq/U6dO7Nmzh4SEhLLb1VdfXfZ3KiYmpiHDd0mWhVutrTxXidkEC6fD2rfVx2Negz6T6hmdEELYn9U9RePGjWPcuHF13n/OnDnExcXxzjvvANC5c2fWrVvHu+++y9ixY629vHBzvWNCOHY2n7TsIrx1Wl6/rlvtq52XGhAbyl+7UmURVyFEJdOnT+fOO++kX79+DBgwgPfee4/8/HymTJkCwKRJk2jRogWzZs3C19eXbt26VTg+JCQEoNL2xkhRlLKFW61Zo+hiGrMR3R/3w/7fAQ2Mfwf6322fIIUQws4cPjFj48aNjBo1qsK2sWPH8thjj1V7TGOr8lMf7ta2Hi2D+XVnCgAPDI2jdahvjbGXb1+vluq4/R0nMiksKsZL5/4V5d3t9bOGJ7cNPLt9jq7w4wg333wzZ86c4YUXXiA9PZ1evXqxePHisuILycnJaLXu/57RELILDeQWq0NTWobamBSV5HNJ0my0uXtBq4fr5kK36+0YpRBC2JfDk6L09PQqKwLl5ORQWFiIn59fpWMaW5Ufe3CXthkLQafREekHrQsOsWjRoTodFx8fj1kBP52O/BITn/+ymJhABwfbgNzl9bOFJ7cNPLt9jqrw4yjTpk1j2rRpVT63atWqGo+dP3++/QNyUyfPqxOYmwX54Oets/4EBefRfXsjEbl7UfT+aG7+CtqNqv04IYRwIpcs4dXYqvzUhzu2bfTIQpr46Qnwqf2/38Xt+/38DlYfPot3y65cOdj9Vz93x9evrjy5beDZ7XN0hR/h2pLrM3QuJw2+vg7t6f2U6ALQ3vozXnGD7RyhEELYn8OToqioqCorAgUHB1fZSwSNr8qPPbhT21o3sz5OS/sGtmnK6sNn2Xkym/vcpL114U6vn7U8uW3g2e1zVIUf4drKiiyEVv03ulrnjsJXEyErGSUwknUtH+Wylv3tH6AQQjiAwwdYDxo0qEJFIFCHZFRVEUiI2vS3VKA7nomiKE6ORgghPM/JTBt6itL3wLwrICsZQuMw3rmIXL+WDopQCCHsz+qkKC8vr6yEKagltxMSEkhOTgbUoW+TJl0ot/nAAw+QlJTEU089xcGDB/noo4/48ccfefzxx+3TAtGodG/RBG+dlrN5xRw/5z5zHYQQwl1YKs+1rGtSdGIDfDEe8k9DZHe4awmEuP/wZiFE42J1UrRt2zZ69+5N7969AbUMau/evXnhhRcASEtLK0uQAOLi4li4cCHx8fH07NmTd955h88++0zKcQub+Op19IxpAsDW41KaWwgh7M2qctyHl8BX10JxNrQaBJP/hqDI2o8TQggXY/WcouHDh9c4bKmqCj7Dhw9n586d1l5KiCr1iw1j6/FMth47z039ZJFFIYSwF5NZ4VSmWlyj1qRo94/w2wOgmKD9WLhxPnjXc7FXIYRwElm0QbidAaXziradyHRyJEII4VnSsgsxmhX0Og2Rwb7V77h5Lvx6r5oQdb8J/vWNJERCCLcmSZFwO31ah6LRwLGz+ZzOLXJ2OEII4TEsaxS1DPVHp9VU3kFRYOUs+Ocp9fGA++HauaDzzAqMQojGQ5Ii4Xaa+OnpGBkEwPbj0lskhBAWZ/OKueHjDfy47aRNx1vmE8VUNXTObFaTodVvqI+H/wfGvQla+SghhHB/8k4m3FL50txCCCFU8fsz2HYik7eXHMJstn7ZgmrXKDIZ1OFyWz5RH497C4Y/DZoqepOEEMINSVIk3FL/OEtSJBXohBDC4lTpGkOnc4vZnZJt9fFVrlFUUgDf3QJ7fwatF1z3GQy8zy7xCiGEq5CkSLil/rGhAOxLzSav2OjkaIQQwjWklFaOA1i2P8Pq45MvHj5XmKWW3E6MBy8/+Nd30ONGe4QqhBAuRZIi4Zaim/jRIsQPswI7k2UInRBCAKRkXUiK4m1IiiyFFlqF+UNuOswfDyc3gU8TuOM36DDGbrEKIYQrkaRIuK0BcTKvSAghyjtVrqfoUEYuyecK6nxsQYmRs3nFALTyyoR5YyFjLwREwJRF0HqQ3eMVQghXIUmRcFv9SofQbT0m84qEEMJgMpORoy5T0C4iEIBlB+reW2TpJQr29SJ441uQeRxCWsPdSyCqm93jFUIIVyJJkXBblkVcd57MxGAyOzkaIYRwrvTsIswKeHtp+Vf/GMC6IXSWctxtwvRw4C9148SPIKyN3WMVQghXI0mRcFttmwUS4q+nyGBmX2qOs8MRQginslSOaxnix+gukQBsOX6e7AJDnY63FFkY67MXirMhKBpaDXZMsEII4WIkKRJuS6vV0K916bwiGUInhGjkLJXnWoT60bppAB0iAzGZFVYeOl2n4y1J1dDiteqGrtfJwqxCiEZD3u2EW7OU5pb1ioQQjZ2lyEKLEHXh1VGd1d6i+DrOKzp5vgA/iuiYvUbd0O06+wcphBAuSpIi4dYsi7huO5GJoli/ersQQngKSznulqFqUmQZQrf60BmKjaZaj08+X8Dl2gS8TEUQ0gpa9HVcsEII4WIkKRJurVvzJvh4aTmfX8LRM/nODkcIIZym/PA5gJ4tQ2gW5ENesZHNSTX3piuKwsnzhUzQbVQ3dLseNBqHxiuEEK5EkiLh1ry9tPSKCQFkCJ0QonE7lVVaaCHUH1DnXY7qHAHUXoXubF4JOkMuI7QJ6oZu1zssTiGEcEWSFAm3d2ERV0mKhBCNk8mskJalrlFkmVMEF+YVLTuQUeMQ4+TzBYzWbsdHY4DwDhAp6xIJIRoXSYqE2+tXul7RtuOZTo5ECCGcIyOnCKNZwUurITLYt2z7kHbh+Ol1pGUX1bh0wanMAhk6J4Ro1CQpEm6vT6sQtBr1m07Lau61MZrMGGXBVyGEh7AUWYgO8UWnvZDQ+Op1XNY+HKh5CN3p9FQu0+5RH3SVqnNCiMZHkiLh9oJ89XSODgaqH0JnNivsT83hs7VJ3D1/K71ejqffa8vKVnAXQgh3lnJROe7yLFXoltVQmjs0eQl6jYkzAR2gWQfHBCmEEC7My9kBCGEP/WPD2Jeaw9Zj57mqR3MURSHpbD4bjp5j49GzbDx6jswqVnWfu+Yor07s7oSIhRDCfk5lViyyUN7lnSLQaGBfag4pWYVVJk6dzsYDcLrVeJo5NlQhhHBJkhQJj9A/Noz5G46z7MBpcooS2HD0LBk5xRX2CfDWMSAujMFtwwn28+LpX/bw47ZTPDqyA82CfJwUuRBC1J9l+FxVCU/TQB/6tgpl24lMlh/IYNKg2Io75GbQuWQ3AOau1zo6VCGEcEmSFAmP0D82FFA/GPy2MwVQy3X3bRXK4LZNGdyuKT1ahqDXqSNGFUXh+60n2Zmcxbz1x3j6ik5Oi10IIerr1EVrFF1sdJdItp3IJH5/5aTIuPc3vDCz09yOFq07OjpUIYRwSZIUCY8QEezLvZfFkXAyq6w3qG/rUHz1uir312g0PDS8Hfcu2MbXG0/w4PC2BPvqGzhqIYSwD8ucopbVJEWjukQy65+DbEo6R06RocL7nXH3z3gB/zCYGYHSay6EaJwkKRIe49nxXazaf2SnCNpHBHLkdB5fbzrBQ8PbOSgyIYRwHEVRyobPtQypPKcIoG2zQNo0CyDpTD6rD51hQs/m6hNZJ/FN24pZ0bA7eAQaKcUthGikpPqcaLS0Wg0PDm8LwLx1xygymJwckRBCWO9MXjHFRjMaDUQ18a12v9Gdq6hCt+83ALYonQgIj3FonEII4cokKRKN2oSezWkR4sfZvBJ+2nbS2eEIIYTVLEPnooJ98faq/s+6pTT3yoOnMVjWadv7CwB/my4hJqzqXiYhhGgMJCkSjZpep+W+oW0AmLsmSRZ0FUK4nZoqz5XXu1UoTQO8ySkysvXYeTh3FNISMKHlH9MASYqEEI2aJEWi0bupXwxNA7w5lVnI37vTnB2OEEJY5VQtRRYsdFoNl3eKAGDp/gzY+ysAu/S9OEcTWklSJIRoxCQpEo2en7eOKUNiAfh41VHMZsW5AQkhhBVSainHXd6oLhfmFSn71KTod8MlAMSE1X68EEJ4KkmKhADuGBRLoI8XhzJyWXnotLPDEUKIOjuVWQDA/7d353FR1/kDx18zAzOAMAIilyheqKiIikdomiaKVq5amZm7pZX9Kl3bn+mWHV5t2Wm5Xe7WT61NszK1djVXRMmLNK+8UVTEg0NU5GYG5vP7Y2IKBQUFhhnez8eDB8z3mvfbQT7zns/n+/k0q2Tmud/rF+aHwUWLR/YxNJmHUVpXVhd1A6C5j/QUCSEaLimKhAAau7sy7rYWAHyUcAKlpLdICOEYbNNxV6GnyEPvwu1t/bhHlwhAbvMB5NAIP089jQyySocQouGSokiIXz3WtxV6Fy27T19m56lL9g5HCCFuSClVreFzADHh/gzXWouiUwGxAIRIL5EQooGTokiIX/kb3bg/KgSAj388YedohBDixrILzOSbrGus3Wj2uTKxTdJppc2gUOn5wWwdOieTLAghGjopioT4nf/p3xqtBhKSLnDo/BV7hyOEENdVNnTOz9OAm6uuSuf4nvw3APGWbizdcxGQSRaEEEKKIiF+J7RJI+7uEgxYZ6ITQoj67Gw1h85hscCh1QD8uzSa3OISQHqKhBBCiiIhrvLUHW0AWHsgjZSsfDtHI4QQlSubea4qkyxYT/gZrpyh1NWTBEtX22ZZuFUI0dBJUSTEVToGGxnYvikWBf/YfNLe4QghRKVsM89V8X4iDn4LgDb8bgKbeNs2y3TcQoiGTooiISrw1IC2AHy7+ywZOUV2jkYIISpWreFzllI4tAoATef7iQm3LuTqotUQ1Nit1mIUQghHIEWREBXo1cqXHqE+mEot/N/WU/YORwghKlQ2HXeVhs+lbIX8THD3gdYDuCsiEICwAC9cdPJ2QAjRsMlfQSEq8fRA671FS386zZUCs52jEUKIa5UNn2vmXYXhb78OnSN8OLjoiQr15bNHe/HBQ91qMUIhhHAMUhQJUYmB7f3pEOhFvqmUzxNT7B2OEEKUk1tk5kqh9QObGw6fKzXDke+tP3e+z7b5jnZNadPUs7ZCFEIIhyFFkRCV0Gg0PDXA2lu0eHsKhb8ukCiEEPVBWS+Rt4crngaX6x98MgEKL0Mjf2jZr/aDE0IIByNFkRDXcXdEEM193bmUb2LNgTR7hyOEEDZnL5UNnavC/URlQ+c6jQRt1RZ5FUKIhkSKIiGuw0WnZVS3EADiDqfbORohhPiNbTruGw2dMxfBkf9Yf/7d0DkhhBC/uami6MMPP6Rly5a4ubnRu3dvdu7cWemxS5YsQaPRlPtyc5OpP4XjGNLROm3t5mNZFJllCJ0Qon6o8iQLyXFgygVjCIT0qoPIhBDC8VS7KPrqq6+YOnUqs2bNYs+ePURGRhIbG0tmZmal5xiNRtLS0mxfp0+fvqWghahLnYKNBDV2o9BcyrbkLHuHI4QQAJy9XABUYZKFsqFznUeBVgaICCFERar913H+/PlMnDiRCRMm0LFjRxYuXIiHhweLFi2q9ByNRkNgYKDtKyAg4JaCFqIuaTQa2yKHcYcz7ByNEEJYVWmNouI8SFpn/bnTvXUQlRBCOKYbTFdTnslkYvfu3cyYMcO2TavVEhMTQ2JiYqXn5eXlERoaisVioXv37rz22mt06tSp0uOLi4spLi62Pc7JyQHAbDZjNld/vZiyc27m3PrOmXOD+pPfwPZN+NdPp9lwJIPiYhNaraZGrltf8qsNzpwbOHd+v8/NGfNzFr8Nn7tOUXRsHZQUgk8rCJb1iIQQojLVKoqysrIoLS29pqcnICCAo0ePVnhO+/btWbRoEV26dOHKlSu8/fbb9OnTh0OHDhESElLhOfPmzWPOnDnXbF+/fj0eHlVYoK4ScXFxN31ufefMuYH98yuxgJtOR1aeiY+/+YFWXjV7fXvnV5ucOTdw7vzi4uIoKCiwdxiiAoWmUrLyTAA097lOu3hwpfV75/tAUzMf5gghhDOqVlF0M6Kjo4mOjrY97tOnD+Hh4fzjH//glVdeqfCcGTNmMHXqVNvjnJwcmjdvzpAhQzAajdWOwWw2ExcXx+DBg3F1da1+EvWYM+cG9Su/jfn7WXMwnQKfttw1pF2NXLM+5VfTnDk3cO78fp9bYWGhvcMRFSjrJfI0uGB0r6QpL8y2TrIAMuucEELcQLWKIj8/P3Q6HRkZ5e+ryMjIIDAwsErXcHV1pVu3biQnJ1d6jMFgwGAwVHjurbz5uNXz6zNnzg3qR35DOgey5mA6G5OyeOHuyod/3oz6kF9tcebcwLnzc3V1paSkxN5hiArYJlnwdkdTWQ/Q0TVQaoKm4RDQsQ6jE0IIx1OtiRb0ej1RUVHEx8fbtlksFuLj48v1Bl1PaWkpBw4cICgoqHqRCmFnA9r746LVkJyZx6msfHuHI4RowKq0RpFt1jnpJRJCiBup9uxzU6dO5ZNPPuGzzz7jyJEjPPXUU+Tn5zNhwgQAHn744XITMcydO5f169dz8uRJ9uzZwx//+EdOnz7N448/XnNZCFEHGru7clvrJoAs5CqEsK+ymecqnY47PwtOJlh/7iyzzgkhxI1U+56iMWPGcOHCBWbOnEl6ejpdu3Zl3bp1tskXUlNT0f5uHYTLly8zceJE0tPT8fHxISoqiu3bt9Oxo3TlC8czuGMAW5OziDucwRP929g7HCFEA3X2RtNxH/4OVCkERUIT+VslhBA3clMTLUyePJnJkydXuC8hIaHc43fffZd33333Zp5GiHonpmMAs74/xO7Tl7mYV0wTz2vvfRNCiNr223Tclcw8d2iV9bsMnRNCiCqRpa2FqIZm3u50CjZiURB/NNPe4QghGijbRAsV9RTlpEHKVuvPnUbVYVRCCOG4pCgSopoGd7QOFY07nHGDI4UQouaZSixk5loXOK9w+Nzh1YCC5r3Bu0WdxiaEEI5KiiIhqqmsKNpy/AKFplI7RyOEaGjSrhSiFLi5amnSSH/tATLrnBBCVJsURUJUU8cgI8283SkyW9ianGXvcIQQDUzZJAvBFa1RdDkFzv4MGi10HFnnsQkhhKOSokiIatJoNL8bQidTcwsh6tY528xzFUyyUDbBQsvbwSugDqMSQgjHJkWREDehrCiKP5JJqUXZORohBMCHH35Iy5YtcXNzo3fv3uzcubPSYz/55BP69euHj48PPj4+xMTEXPf4+uSsbea5Cu4nkqFzQghxU6QoEuIm9Grli9HNhYv5JvamXrZ3OEI0eF999RVTp05l1qxZ7Nmzh8jISGJjY8nMrHiWyISEBMaOHcumTZtITEykefPmDBkyhHPnztVx5NVXNvPcNZMsZB2H9AOgdYHwP9ghMiGEcFxSFAlxE1x1WgZ28AdkFjoh6oP58+czceJEJkyYQMeOHVm4cCEeHh4sWrSowuOXLl3K008/TdeuXenQoQOffvopFouF+Pj4Oo68+s5VtnDrwZXW760HgodvHUclhBCO7aYWbxVCWIfQfbfvPHGHM5hxV7i9wxGiwTKZTOzevZsZM2bYtmm1WmJiYkhMTKzSNQoKCjCbzfj6VlxMFBcXU1xcbHuck5MDgNlsxmw2VzvmsnNu5tyynqIAT9ffzlcKlwPfoAFKwkeibuK6NelW8nMEzpyfM+cGzp2fM+cG5fOrjRylKBLiJt3RrimuOg0ns/JJzsyjrb+nvUMSokHKysqitLSUgIDyEwsEBARw9OjRKl3jueeeIzg4mJiYmAr3z5s3jzlz5lyzff369Xh4VDDhQRXFxcVV6/hSBWnZOkBD0p5EMg5ZtxsLUxl48TilGlf+e1pHydm1Nx1TTapufo7GmfNz5tzAufNz5tzAml9BQUGNX1eKIiFukpebK9Ft/Nh87AJxhzOkKBLCQb3++ussX76chIQE3NzcKjxmxowZTJ061fY4JyfHdh+S0Wis9nOazWbi4uIYPHgwrq6uVT7vfHYhlp+24KrT8OCIYWi11im5tXEvAaBpN4Qhw++vdjw17WbzcxTOnJ8z5wbOnZ8z5wbl8yssLKzx60tRJMQtGNwx4NeiKJ2nBrSxdzhCNEh+fn7odDoyMsrf35eRkUFgYOB1z3377bd5/fXX2bBhA126dKn0OIPBgMFguGa7q6vrLb35qO756bnWYXvB3u4YDL8u3JqTBnuWAKCNGo+2Hr0ZutV/n/rOmfNz5tzAufNz5tzAml9JSUmNX9dpiiKLxYLJZKpwn9lsxsXFhaKiIkpLS+s4strlTLm5urqi0+nsHUa1DA4P4OXVB9l7JpsLucU09br2TVNNyC4w8fWuMwzpGEhLv0a18hxCOCq9Xk9UVBTx8fGMHDkSwDZpwuTJkys978033+TVV1/lv//9Lz169KijaG/NuYqm4978FpQUQfPeEDbYTpEJIYRjc4qiyGQycerUKSwWS4X7lVIEBgZy5syZa1f/dnDOlpu3tzeBgYEOk0tgYze6hDRm/9krxB/J4MFeLWr8OZIzc3nss12cvljA0h2p/Pcv/XFzdaziUYjaNnXqVB555BF69OhBr169eO+998jPz2fChAkAPPzwwzRr1ox58+YB8MYbbzBz5kyWLVtGy5YtSU+3LsTs6emJp2f9HQp79vJVRdHlFNjzmfXnO18GB/nbKYQQ9Y3DF0VKKdLS0tDpdDRv3hyt9tpZxi0WC3l5eXh6ela435E5S25KKQoKCmxrigQFBdk5oqobHB7A/rNXiDtc80XRpqOZTPlyL7nF1m7i0xcL+Ofmk0wZFFajzyOEoxszZgwXLlxg5syZpKen07VrV9atW2ebfCE1NbXc38iPP/4Yk8nE/feXv/9m1qxZzJ49uy5Dr5bfpuP+dXKHhDfAUgKtB0CrfvYLTAghHJzDF0UlJSUUFBQQHBxc6QxAZUPr3NzcHLpwqIgz5ebubv3kMzMzE39/f4cZSje4UwDvxB1ja3IWBaYSPPS3/t9KKcUnW04y74ejKGVdLPbuiCBmfX+IDzclM6pbM5r73vyMV0I4o8mTJ1c6XC4hIaHc45SUlNoPqBbYhs/5uMOFJNi/3Lrjzpl2jEoIIRyfY7+LBtt9NHq93s6RiJpQVtg60hz77QO8aO7rTnGJhc3Hsm75esUlpUz7Zj+vrbUWRGN7NeeLx3rzcHQo0a2bUFxiYc6/D9VA5EIIR1O2RlEzb3fY9CooC7S/G0Ki7ByZEEI4Nocviso4yj0o4voc8XXUaDQMDrfOcBV3OOMGR19fZm4RY//5E9/uOYtWA7OHd+S1URHoXbRoNBpeGdkJF62GDUcy2XCLzyWEcCwWi+J8dhEArczH4fB3gAbufNG+gQkhhBNwmqJICHsa3NF638LGoxmUlFY84ceNHDx3hZEfbGNPajZGNxc+e7QX4/u2KlcotvX34rF+rQCY/e9DFJoce8ZBIUTVZeUVYyq1oNWA/663rBsj7oeATvYNTAghnIAURU6gS5cuLFiwoEaulZCQgEajITs7u0au11D0bOmDt4crlwvM7D59udrn/3AwndELEzl/pYjWfo1YPakv/cKaVnjslDvDCGrsxtnLhXyckHyroQshHMSZXydZGOKVgjZ5A2h0MGCGnaMSQgjnIEWRnQwYMIC//OUvNXKtjRs3MnHixBq5lrg5Ljotd7b3B6o3hM5iUfxwRsOUr/ZTaC6lf7umrJrUl9ZNK58SuJHBhZfv6QjAwh9Pcior/9aCF0I4BOskC4op6kvrhm5/hCayaLQQQtQEKYrqKaVUlVfr9fPzq3TmPVF3yobQxR3JQCl1zX6lFLlFZk5fzGf36cvEHc5gyle/sO6sdZa9R/u2YtEjPWjsfuNVqId1DqRfmB+mUguzvj9U4fMJIZzL2csF9NMeoKP5AOj0cMdf7R2SEEI4DSmK7GD8+PH8+OOPLFiwAI1Gg0ajYcmSJWg0Gn744QeioqIwGAxs3bqVEydOMGLECAICAvD09KRnz55s2LCh3PWuHj6n0Wj49NNPGTVqFB4eHoSFhfH999/fdLzffvstnTp1wmAw0LJlS955551y+z/66CPCwsJwc3MjICCg3LofK1asICIiAnd3d5o0aUJMTAz5+c7Zs9G/XVP0Oi2nLxbwwqqD/GX5Xv70fzu4a8EWbnstnvYvrSNi9nrueCuB+z7ezsTPd/Hfw5noNIrXRnZk5vCOuOiq9l9So9Ewd0Rn9Dotm49d4L+H0ms5OyGEvZ27VMA0l6+tD3o8Bo1D7BuQEEI4EYdfp+hqSikKzeVvPrdYLBSaSnExldTqWj7urroqzZ62YMECjh07RufOnZk7dy4Ahw5Zp1h+/vnnefvtt2ndujU+Pj6cOXOGu+66i1dffRWDwcDnn3/O8OHDSUpKokWLyhcKnTNnDm+++SZvvfUW77//PuPGjeP06dP4+vpWK6fdu3fzwAMPMHv2bMaMGcP27dt5+umnadKkCePHj2fXrl1MmTKFf/3rX/Tp04dLly6xZcsWANLS0hg7dixvvvkmo0aNIjc3ly1btjhtr0Yjgwt92zZhU9IFvtyZWulx7q46fBvpaeKpx99TTyddGqOjqv/mppVfI57o35oPNiUz99+H6d+uaY2skSSEqJ8C0jYQqT2JWeeOa7+p9g5HCCGcitO9gyo0l9Jx5n/t8tyH58ZW6U1p48aN0ev1eHh4EBhoncr56NGjAMydO5fBgwfbjvX19SUyMtL2+JVXXmHVqlV8//33lS5SCNbeqLFjxwLw2muv8fe//52dO3cydOjQauU0f/58Bg0axMsvvwxAu3btOHz4MG+99Rbjx48nNTWVRo0acc899+Dl5UVoaCjdunUDrEVRSUkJ9957L6GhoQBERERU6/kdzYt3d6SFbwpueh1NGulp0siAr6eeJo301kKokQF3/W+L0prNZtauTbvp55s0sC2r9p7jXHYh729M5rmhHWoiDSFEfWMpZfjFxQCkdxhPc09/OwckhBDORYbP1TM9evQo9zgvL49p06YRHh6Ot7c3np6eHDlyhNTUynsiwDqkrkyjRo0wGo1kZmZWO54jR47Qt2/fctv69u3L8ePHKS0tZfDgwYSGhtK6dWv+9Kc/sXTpUgoKrIsLRkZGMmjQICIiIhg9ejSffPIJly9Xf2Y2R9LW35M5IzozY1g4T/Rvw31RIQxs70+XEG9CfDzKFUQ1wV2vY/YfrNPxfrrlJMmZeTV6fSFE/aAOrKCVJZUrygNL9J/tHY4QQjgdp+spcnfVcXhubLltFouF3JxcvIxetT587lY1atSo3ONp06YRFxfH22+/Tdu2bXF3d+f+++/HZDJd9zquruVv1tdoNFgsN7d+zvV4eXmxZ88eEhISWL9+PTNnzmT27Nn8/PPPeHt7ExcXx/bt21m/fj3vv/8+L774Ijt27KBVq1Y1HktDFRPuz50d/Nl4NJOZ3x1k6eO9HXIRXCFEJUrNWDa+ig74R8k9TAkItHdEQgjhdJyup0ij0eChd7nmy12vq3B7TX5V542oXq+ntPTGC29u27aN8ePHM2rUKCIiIggMDCQlJeUW/oWqJzw8nG3btl0TU7t27dDprEWgi4sLMTExvPnmm+zfv5+UlBQ2btwIWF+Pvn37MmfOHPbu3Yter2fVqlV1Fn9DoNFomD28EwYXLdtPXOQ/+29+OJ4Qoh7a+wW6K6e5oIyscf8DbjXwAZwQQojynK6nyFG0bNmSHTt2kJKSgqenZ6W9OGFhYaxcuZLhw4ej0Wh4+eWXa6XHpzLPPvssPXv25JVXXmHMmDEkJibywQcf8NFHHwHwn//8h5MnT9K/f398fHxYu3YtFouF9u3bs2PHDuLj4xkyZAj+/v7s2LGDCxcuEB4eXmfxNxQtmnjw9IC2vLvhGH9bc5iBHfzxNMh/byEcnrkIfnwTgI9KRuAbUL3JcoQQQlSN0/UUOYpp06ah0+no2LEjTZs2rfQeofnz5+Pj40OfPn0YPnw4sbGxdO/evc7i7N69O19//TXLly+nc+fOzJw5k7lz5zJ+/HgAvL29WblyJXfeeSfh4eEsXLiQL7/8kk6dOmE0Gtm8eTN33XUX7dq146WXXuKdd95h2LBhdRZ/Q/I/d7QmtIkHGTnFLNhwzN7hCCFqwq7/g9zz5BkCWFY6iGbe7vaOSAghnJJ8lGwn7dq1IzExsdy2skLj91q2bGkbilZm0qRJ5R7v378fo9Foe1zRlNfZ2dlVimvAgAHXnH/fffdx3333VXj87bffTkJCQoX7wsPDWbduXZWeV9w6N1frpAsTFv/Mom0p3B/VnPaBXvYOSwhRgYv5JnZmaoi1KCpdrrk4D7bMB2BjwHiKr+gJ8ZGFuoUQojZIT5EQTmRge39iOwVQalFM+XIvaVcK7R2SEKICHyWcZOkJHSM+SmRTUmbF67ft+BgKssC3NWu0dwLQzEd6ioQQojZIUdTAPPnkk3h6elb49eSTT9o7PFEDZg7vhJ+ngaSMXEZ9uJ3D53PsHZIQ4irNfd1x1ymSMvKYsPhnHvpkB/vPZv92QOFl2Pa+9ecBL3A62zrjaIgURUIIUStk+FwDM3fuXKZNm1bhvt8PwROOq5m3O6ue7sOEJT+TnJnHA/9I5MNx3bmjXVN7hyaE+NX46FAaZR7ihKEN/9pxhsSTF/nDB9sYHhnM9CHtabH371B8Bfw7Qef7OLcyDoAQuadICCFqhRRFDYy/vz/+/rISurNr7uvBt0/24X++2MVPJy/x6JKfeXVkZx7s1cLeodVrOUVmjG6V3uEhRI1q5ArPD23PhNtbMz/uGKv2nuPfv5xn18Ej/Gj4CD3AnS9ypbiU3KISQIbPCSFEbZHhc0I4qcYernz+aG/u7daMUovi+ZUHeOu/R7FYKrh3QfB5Ygpd56xn0rI9mEvrbtp7IUJ8PJj/QFfW/Lkf/ds15QnNavSWIvartnxwLozkzDwAfBvp8dDLZ5lCCFEb5K+rEE5M76LlnQciCfH14O/xx/lw0wnOXCrkrdFdMLjIApBlEpIymf39ISwK1vy6+O2CMV1x0cnnRqLudAw28vm9QVj+vgks8IZ5NNvijmPYdAJApuMWQohaJC2+EE5Oo9EwdXA73rq/Cy5aDd//cp4/fbqT7AKTvUOrF5Izc/nzsr1YFPRp0wRXnYY1+9N49ptfKJVeNVHXfnwDrcWECr2dB0b/kRAfd4pLrD2XUhQJIUTtkaJIiAZidI/mfPZoL7wMLuxMucS9H28n9WKBvcOyq8v5Jh77bBe5xSX0aunL4gk9+fCh7rhoNXy37zx/XbFfhhuKunPxBOxbBoBm0ExGdAsh/tk7ePmejnQKNvJAzxA7ByiEEM5LiiIhGpC+bf1Y8VQfghu7cfJCPqM+2sbe1Mv2DssuTCUWnvxiN6cvFhDi487Hf+yOwUXHkE6BvD+2Gzqthm/3nGXGygNSGIm6sek1UKUQFgstegNgcNHx2O2tWDOlH3d2CLBzgEII4bykKHJgLVu2ZMGCBVU6VqPRsHr16toNSDiE9oFerJrUl87NjFzMN/HgP39i+c5Uisyl9g6tziilmPX9QXacukQjvY7/e6QnTTwNtv3DIoJ4b0xXtBr4atcZXv7uYMWLawpRU9IPwsEV1p/vfMm+sQghRAMkRZEQDVCA0Y2vnojmzg7+FJdYeH7lAXq+uoEZK/ezK+VSnRcA+cUlXM6vu3ucFm9L4cudZ9Bo4P2HutE+0OuaY4ZHBvPOA5FoNLB0Rypz/n1YCiNReza9av3eaRQEdbFvLEII0QDJ7HNCNFCNDC78809R/GPzSZbtSOVcdiFf7jzDlzvP0MqvEfd2a8ao7s0I8fGo0ectMpdyJC2H/Wev/PqVTfIF65TD93UPYXpsewKMbjX6nL+3KSmTv605DMALw8KvOyRpVLcQSkoVf/12P0u2p+Ci1fDi3eFoNJpai080QGd3QdJa0GhhwAv2jkYIIRok6Smyk3/+858EBwdjsZRfD2XEiBE8+uijnDhxghEjRhAQEICnpyc9e/Zkw4YNNfb8Bw4c4M4778Td3Z0mTZrwxBNPkJeXZ9ufkJBAr169aNSoEd7e3vTt25fTp08D8MsvvzBw4EC8vLwwGo1ERUWxa9euGotN1B0XnZZJA9uy5a8DWTaxN/d1D8FDr+NUVj7vxB3j9jc2MfafP7Fi91nyi0uqff1SCxw6n8OXO1OZsXI/d/99C51n/ZdRH21n1veH+HbPWY5n5qEUKAUrdp9lwFsJLNhwnAJT9Z/vRpIzc5ny60xzD/QI4fF+rW54zugezZk3KgKAT7ee4o11SdJjJGpW/Fzr98ix0LSdfWMRQogGyvl6ipQC81Uzalks1m0mHWhrsQ509YAqfoI8evRo/vznP7Np0yYGDRoEwKVLl1i3bh1r164lLy+Pu+66i1dffRWDwcDnn3/O8OHDSUpKokWLFrcUZn5+PrGxsURHR/Pzzz+TmZnJ448/zuTJk1myZAklJSWMHDmSiRMn8uWXX2Iymdi5c6ft0/Fx48bRrVs3Pv74Y3Q6Hfv27cPV1fWWYhL2pdVq6NPGjz5t/Jg7ohPrDqbz7Z6zbD9xkcST1q+Z3x1kaOdAeoT6UmgupaC4hHxTKQWmEvKLf/1u+m17frGZtMs6zDt+uub5mjTS0yWkMV1CvOkS0piIkMacvVzI3/5zmD2p2by74Rhf7kxlemx7RnVrhlZ76z0zl/NNPLrkt5nm/jYyoso9Pg/2aoHZonh59UEW/ngCV52GKQNb33JMZUwlFnadvsSmo5lsPpaFRgO3t/WjX7um9Grpi7te1pRyVpqULXDqR9C6wh3P2TscIYRosG6qKPrwww956623SE9PJzIykvfff59evXpVevw333zDyy+/TEpKCmFhYbzxxhvcddddNx30dZkL4LXgcpu0gHftPFt5L5wHfaMqHerj48OwYcNYtmyZrShasWIFfn5+DBw4EK1WS2RkpO34V155hVWrVvH9998zefLkWwpz2bJlFBUV8fnnn9OokTXeDz74gOHDh/PGG2/g6urKlStXuOeee2jTpg0A4eHhtvNTU1OZPn06HTp0ACAsLOyW4hH1SyODC/dFhXBfVAhnLxewas85vt1zlpSLBazcc46Ve85V42oajG4udAnxJiKkMZEhjYkI8Sa4sds1BYm/lxvfPtWH/+xP4/UfjnIuu5Bnv/mFJdtTeOnucHq3bnLTOZXNNJd66beZ5vQu1fuA5E+3hVJaamH2vw/z/sZkNCja3HREkJlbRELSBTYdzWTL8SzyruqJO5qey6dbT6F30dKrpS+3h/nRL8yP8EBjjRSJoh5QCm3Ca9afo8aDT6hdwxFCiIas2kXRV199xdSpU1m4cCG9e/fmvffeIzY2lqSkJPz9/a85fvv27YwdO5Z58+Zxzz33sGzZMkaOHMmePXvo3LlzjSThqMaNG8fEiRP56KOPMBgMLF26lAcffBCtVkteXh6zZ89mzZo1pKWlUVJSQmFhIampqbf8vEeOHCEyMtJWEAH07dsXi8VCUlIS/fv3Z/z48cTGxjJ48GBiYmJ44IEHCAoKAmDq1Kk8/vjj/Otf/yImJobRo0fbiifhXEJ8PPjzoDAm39mWPamXWbX3HGnZRTQyuNDIoMND70IjvQ4Pw6/f9b9tN+jg4K7t/GnUYPR6fZWeT6PRMDwymMEdA1i8LYUPNyVz4NwVxvzzJ4Z2CuT5YR1o6Ve1Dx7K/H6mOU+DyzUzzVXH+L6tKLEo/rbmCH/feILWXjri8w8Q5O2Ov9GNAKOBQKMbAUY3mnoZcHP9rYfHYlHsP3eFjUczSUjKZP/ZK+Wu7eep5452/gzs0BSALcey2HL8AuevFLE1OYutyVm8/oP1uNvb+nF7WFP6hfnV6v1XonYF5OxDe+5ncHGH/tPsHY4QQjRo1S6K5s+fz8SJE5kwYQIACxcuZM2aNSxatIjnn3/+muMXLFjA0KFDmT59OmDt8YiLi+ODDz5g4cKFtxh+BVw9rD02v2OxWMjJzcXo5YW2tofPVcPw4cNRSrFmzRp69uzJli1bePfddwGYNm0acXFxvP3227Rt2xZ3d3fuv/9+TKa6maFr8eLFTJkyhXXr1vHVV1/x0ksvERcXx2233cbs2bN56KGHWLNmDT/88AOzZs1i+fLljBo1qk5iE3VPo9EQFepLVKhvlc8xm82kH+SmJiVwc9Xx1IA2jO4Rwrtx1qF06w6lE380g0eiW/LnQWE0dq/akM1Fv5tp7u9ju1Y401x1PN6vNaZSC2+uS+JkroaT+9MqPdbbw5UALzf8vPQkpeeSlVf+/2+XkMYMbO/PwA7+dGnWuFwP0D1dglFKceJCPluPX2DL8SwST14kK8/E6n3nWb3P+neufYAX88dE0im48S3lJeqYshCe9q31595PgFegfeMRQogGrlpFkclkYvfu3cyYMcO2TavVEhMTQ2JiYoXnJCYmMnXq1HLbYmNjr7tmTnFxMcXFxbbHOTk5gPVNltlsLnes2WxGKYXFYvlt0gIX93LHKKXAtRTl6oGlNmeNKrtbvIr0ej2jRo3iiy++4Pjx47Rv356uXbtisVjYtm0bjzzyCCNGjAAgLy+PlJQUW66/PaWyfb960oarlf0btW/fniVLlpCbm2vrLdqyZQtarZawsDDbdSIjI4mMjOS5556jb9++LF261DZMsm3btjzzzDM888wzPPTQQyxatMgW662wWCwopTCbzeh0OtvrffXr7iycOb+ayK2xQcvsezrwUM9mzFt3jK3JF/l06ymW7jiNl5srGg1oNRq0Gmvxpb3qsQY4mZUPwHOx7ejXxrdG/q0n9g3lthZGVm38Cf+W7biYbyYzt5jM3GIycqzfi0ssZBeYyS4wk5RhPc/T4MLtbZtwRzs/7gjzo6nXbz1WpaUllFawVFSoj4HQXiGM6xWCqcTC3jPZbDtxkW3JFzlwPodjmbn4ebjU6O/Q7187Z/zdrA80R76jcWEqyuCFpu9f7B2OEEI0eNUqirKysigtLSUgoPwUtgEBARw9erTCc9LT0ys8Pj09vdLnmTdvHnPmzLlm+/r16/HwKN8b4+LiQmBgIHl5eTfsRcnNzb3ufnsYOXIkDz74IAcPHuSBBx6wFYAtW7ZkxYoVDBw4EIDXXnsNi8WCyWSyHWOxWGzFY1VyKywsJCcnh+HDhzN79mz++Mc/8txzz3Hx4kWmTJnCmDFjcHd358CBAyxZsoRhw4YRGBhIcnIyx44d4/777ycjI4OZM2cyYsQIWrRowfnz59m5cyfDhw+3xXUrTCYThYWFbN68mZKS3+6xiIuLu+Vr12fOnF9N5Ta6KXR20bD6tJb0QguF5uIbn/SraH8LgdmHWbv2cI3EUqZHUyA/iRYAXr9+BVs/GykshSsmuGLSkGMGHz208irBRXsO0s/xc+V/Am+oA9ChBeQHQWqehp9+rLmZKX8vLi6OgoKCGx8oqk37yzIALL2fRudR9R5YIYQQtaNezj43Y8aMcr1LOTk5NG/enCFDhmA0GssdW1RUxJkzZ/D09MTNreKx9UopcnNz8fLyqnfri9xzzz34+vpy/Phxxo8fb8tvwYIFPP7448TGxuLn58df//pXCgsL0ev1tmO0Wi0Gg/WT5qrk5u7ujtFoxGg0sm7dOv73f/+XQYMG4eHhwb333ss777yDp6cn/v7+nDp1ivHjx3Px4kWCgoKYNGkSzzzzDCUlJeTm5vL000+TkZGBn58fo0aNYt68eZX++1dHUVER7u7u9O/fHzc3N8xmM3FxcQwePNgpZ7hz5vxqI7e7gL9YFCez8jGXWlAKLEph+fX7b49/+9nT4ELnYGON/99vKK9dYWGhvcNxSqWjv+DAshfp2OtJZG5BIYSwv2oVRX5+fuh0OjIyMsptz8jIIDCw4vHQgYGB1ToewGAw2N7s/56rq+s1bz5KS0utw2a02krvFyobDlZ2XH2i1Wo5f/78Ndtbt27Nxo0by227eta5lJQU6/1SOTk3zO3qdVUiIyOvuX6ZoKCgSoc3uri4sHz58kqf51ZptVo0Gs01r3VFr70zceb8ajo3V6Bjs6pN3FAXnP21+32PrahBLgZS/O6ko+HW7nETQghRM6pVIej1eqKiooiPj7dts1gsxMfHEx0dXeE50dHR5Y4H65CMyo4XQgghhBBCiLpU7W6TqVOn8sknn/DZZ59x5MgRnnrqKfLz822z0T388MPlJmJ45plnWLduHe+88w5Hjx5l9uzZ7Nq165bX2hG/+frrrzEajXh6el7z1alTJ3uHJ4QQQgghRL1W7XuKxowZw4ULF5g5cybp6el07dqVdevW2SZTSE1NLTeMq0+fPixbtoyXXnqJF154gbCwMFavXt3g1yiqScOGDWPAgAEVDp9z1mE9QgghhBBC1JSbmmhh8uTJlfb0JCQkXLNt9OjRjB49+maeSlSBl5cXzZo1q3f3SwkhhBBCCOEI5F20EEIIIYQQokFzmqLo6tnVhGOS11EIIYQQQtQ1hy+KdDrrCg83WrhVOIayhSLlXighhBBCCFFX6uXirdXh4uKCh4cHFy5cwNXVtcL7aiwWCyaTiaKiIqe778ZZclNKUVBQQGZmJt7e3rZiVwghhBBCiNrm8EWRRqMhKCiIU6dOcfr06QqPUUpRWFiIu7t7ja9qb2/Olpu3t/d1F/YVQgghhBCipjl8UQTWRWXDwsIqHUJnNpvZvHkz/fv3d7phWc6Um6urq/QQCSGEEEKIOucURRGAVqvFzc2twn06nY6SkhLc3NwcvnC4mjPnJoQQQgghRF1w3JtQhBBCCCGEEKIGSFEkhBBCCCGEaNCkKBJCCCGEEEI0aA5xT1HZgp45OTk3db7ZbKagoICcnBynu+/GmXMDyc+ROXNu4Nz5/T63wsJCQBZWvpq0S9cn+TkuZ84NnDs/Z84Nar9tcoiiKDc3F4DmzZvbORIhhGiYcnNzady4sb3DqDekXRJCCPurybZJoxzg4z+LxcL58+fx8vK6qbV4cnJyaN68OWfOnMFoNNZChPbjzLmB5OfInDk3cO78fp+bl5cXubm5BAcHO/QC0TVN2qXrk/wclzPnBs6dnzPnBrXfNjlET5FWqyUkJOSWr2M0Gp3ylwScOzeQ/ByZM+cGzp1fWW7SQ3QtaZeqRvJzXM6cGzh3fs6cG9Re2yQf+wkhhBBCCCEaNCmKhBBCCCGEEA1agyiKDAYDs2bNwmAw2DuUGufMuYHk58icOTdw7vycObf6wtn/jSU/x+XMuYFz5+fMuUHt5+cQEy0IIYQQQgghRG1pED1FQgghhBBCCFEZKYqEEEIIIYQQDZoURUIIIYQQQogGTYoiIYQQQgghRIPm9EXRhx9+SMuWLXFzc6N3797s3LnT3iHdlNmzZ6PRaMp9dejQwba/qKiISZMm0aRJEzw9PbnvvvvIyMiwY8SV27x5M8OHDyc4OBiNRsPq1avL7VdKMXPmTIKCgnB3dycmJobjx4+XO+bSpUuMGzcOo9GIt7c3jz32GHl5eXWYReVulN/48eOveS2HDh1a7pj6mt+8efPo2bMnXl5e+Pv7M3LkSJKSksodU5XfxdTUVO6++248PDzw9/dn+vTplJSU1GUqFapKfgMGDLjm9XvyySfLHVMf8/v444/p0qWLbdG76OhofvjhB9t+R37dHJEztE3O1C6BtE3SNtXPv3HO3C5BPWublBNbvny50uv1atGiRerQoUNq4sSJytvbW2VkZNg7tGqbNWuW6tSpk0pLS7N9Xbhwwbb/ySefVM2bN1fx8fFq165d6rbbblN9+vSxY8SVW7t2rXrxxRfVypUrFaBWrVpVbv/rr7+uGjdurFavXq1++eUX9Yc//EG1atVKFRYW2o4ZOnSoioyMVD/99JPasmWLatu2rRo7dmwdZ1KxG+X3yCOPqKFDh5Z7LS9dulTumPqaX2xsrFq8eLE6ePCg2rdvn7rrrrtUixYtVF5enu2YG/0ulpSUqM6dO6uYmBi1d+9etXbtWuXn56dmzJhhj5TKqUp+d9xxh5o4cWK51+/KlSu2/fU1v++//16tWbNGHTt2TCUlJakXXnhBubq6qoMHDyqlHPt1czTO0jY5U7uklLRN0jbVz79xztwuKVW/2ianLop69eqlJk2aZHtcWlqqgoOD1bx58+wY1c2ZNWuWioyMrHBfdna2cnV1Vd98841t25EjRxSgEhMT6yjCm3P1H2aLxaICAwPVW2+9ZduWnZ2tDAaD+vLLL5VSSh0+fFgB6ueff7Yd88MPPyiNRqPOnTtXZ7FXRWUNz4gRIyo9x5Hyy8zMVID68ccflVJV+11cu3at0mq1Kj093XbMxx9/rIxGoyouLq7bBG7g6vyUsjY+zzzzTKXnOFJ+Pj4+6tNPP3W6162+c5a2yVnbJaWkbaqII+XnzG2Ts7dLStmvbXLa4XMmk4ndu3cTExNj26bVaomJiSExMdGOkd2848ePExwcTOvWrRk3bhypqakA7N69G7PZXC7XDh060KJFC4fL9dSpU6Snp5fLpXHjxvTu3duWS2JiIt7e3vTo0cN2TExMDFqtlh07dtR5zDcjISEBf39/2rdvz1NPPcXFixdt+xwpvytXrgDg6+sLVO13MTExkYiICAICAmzHxMbGkpOTw6FDh+ow+hu7Or8yS5cuxc/Pj86dOzNjxgwKCgps+xwhv9LSUpYvX05+fj7R0dFO97rVZ87WNjWEdgmkbQLHys+Z2yZnbZfA/m2TS82kUf9kZWVRWlpa7h8JICAggKNHj9opqpvXu3dvlixZQvv27UlLS2POnDn069ePgwcPkp6ejl6vx9vbu9w5AQEBpKen2yfgm1QWb0WvW9m+9PR0/P39y+13cXHB19fXIfIdOnQo9957L61ateLEiRO88MILDBs2jMTERHQ6ncPkZ7FY+Mtf/kLfvn3p3LkzQJV+F9PT0yt8fcv21RcV5Qfw0EMPERoaSnBwMPv37+e5554jKSmJlStXAvU7vwMHDhAdHU1RURGenp6sWrWKjh07sm/fPqd53eo7Z2qbGkq7BNI2SdtUP/JzxnYJ6k/b5LRFkbMZNmyY7ecuXbrQu3dvQkND+frrr3F3d7djZKK6HnzwQdvPERERdOnShTZt2pCQkMCgQYPsGFn1TJo0iYMHD7J161Z7h1IrKsvviSeesP0cERFBUFAQgwYN4sSJE7Rp06auw6yW9u3bs2/fPq5cucKKFSt45JFH+PHHH+0dlnBQ0i45F2mb6j9nbJeg/rRNTjt8zs/PD51Od80MFRkZGQQGBtopqprj7e1Nu3btSE5OJjAwEJPJRHZ2drljHDHXsniv97oFBgaSmZlZbn9JSQmXLl1yuHwBWrdujZ+fH8nJyYBj5Dd58mT+85//sGnTJkJCQmzbq/K7GBgYWOHrW7avPqgsv4r07t0boNzrV1/z0+v1tG3blqioKObNm0dkZCQLFixwmtfNEThz2+Ss7RJI2wSOkZ8zt03O2i5B/WmbnLYo0uv1REVFER8fb9tmsViIj48nOjrajpHVjLy8PE6cOEFQUBBRUVG4urqWyzUpKYnU1FSHy7VVq1YEBgaWyyUnJ4cdO3bYcomOjiY7O5vdu3fbjtm4cSMWi8X2h8CRnD17losXLxIUFATU7/yUUkyePJlVq1axceNGWrVqVW5/VX4Xo6OjOXDgQLnGNS4uDqPRSMeOHesmkUrcKL+K7Nu3D6Dc61df87uaxWKhuLjY4V83R+LMbZOztksgbRPU7/ycuW1qaO0S2LFtuvU5Iuqv5cuXK4PBoJYsWaIOHz6snnjiCeXt7V1uhgpH8eyzz6qEhAR16tQptW3bNhUTE6P8/PxUZmamUso6ZWGLFi3Uxo0b1a5du1R0dLSKjo62c9QVy83NVXv37lV79+5VgJo/f77au3evOn36tFLKOu2pt7e3+u6779T+/fvViBEjKpz2tFu3bmrHjh1q69atKiwsrF5MC6rU9fPLzc1V06ZNU4mJierUqVNqw4YNqnv37iosLEwVFRXZrlFf83vqqadU48aNVUJCQrmpPwsKCmzH3Oh3sWz6zCFDhqh9+/apdevWqaZNm9aLqUFvlF9ycrKaO3eu2rVrlzp16pT67rvvVOvWrVX//v1t16iv+T3//PPqxx9/VKdOnVL79+9Xzz//vNJoNGr9+vVKKcd+3RyNs7RNztQuKSVtk7RN9fNvnDO3S0rVr7bJqYsipZR6//33VYsWLZRer1e9evVSP/30k71DuiljxoxRQUFBSq/Xq2bNmqkxY8ao5ORk2/7CwkL19NNPKx8fH+Xh4aFGjRql0tLS7Bhx5TZt2qSAa74eeeQRpZR16tOXX35ZBQQEKIPBoAYNGqSSkpLKXePixYtq7NixytPTUxmNRjVhwgSVm5trh2yudb38CgoK1JAhQ1TTpk2Vq6urCg0NVRMnTrzmzVB9za+ivAC1ePFi2zFV+V1MSUlRw4YNU+7u7srPz089++yzymw213E217pRfqmpqap///7K19dXGQwG1bZtWzV9+vRy60EoVT/ze/TRR1VoaKjS6/WqadOmatCgQbZGRynHft0ckTO0Tc7ULiklbZO0TfXzb5wzt0tK1a+2SaOUUtXrWxJCCCGEEEII5+G09xQJIYQQQgghRFVIUSSEEEIIIYRo0KQoEkIIIYQQQjRoUhQJIYQQQgghGjQpioQQQgghhBANmhRFQgghhBBCiAZNiiIhhBBCCCFEgyZFkRBCCCGEEKJBk6JIiBoyfvx4Ro4cae8whBBCCEDaJSGqQ4oiIYQQQgghRIMmRZEQ1bRixQoiIiJwd3enSZMmxMTEMH36dD777DO+++47NBoNGo2GhIQEAM6cOcMDDzyAt7c3vr6+jBgxgpSUFNv1yj7JmzNnDk2bNsVoNPLkk09iMpnsk6AQQgiHIu2SELfOxd4BCOFI0tLSGDt2LG+++SajRo0iNzeXLVu28PDDD5OamkpOTg6LFy8GwNfXF7PZTGxsLNHR0WzZsgUXFxf+9re/MXToUPbv349erwcgPj4eNzc3EhISSElJYcKECTRp0oRXX33VnukKIYSo56RdEqJmSFEkRDWkpaVRUlLCvffeS2hoKAAREREAuLu7U1xcTGBgoO34L774AovFwqeffopGowFg8eLFeHt7k5CQwJAhQwDQ6/UsWrQIDw8POnXqxNy5c5k+fTqvvPIKWq106AohhKiYtEtC1Az5rRaiGiIjIxk0aBARERGMHj2aTz75hMuXL1d6/C+//EJycjJeXl54enri6emJr68vRUVFnDhxotx1PTw8bI+jo6PJy8vjzJkztZqPEEIIxybtkhA1Q3qKhKgGnU5HXFwc27dvZ/369bz//vu8+OKL7Nixo8Lj8/LyiIqKYunSpdfsa9q0aW2HK4QQwslJuyREzZCiSIhq0mg09O3bl759+zJz5kxCQ0NZtWoVer2e0tLScsd2796dr776Cn9/f4xGY6XX/OWXXygsLMTd3R2An376CU9PT5o3b16ruQghhHB80i4Jcetk+JwQ1bBjxw5ee+01du3aRWpqKitXruTChQuEh4fTsmVL9u/fT1JSEllZWZjNZsaNG4efnx8jRoxgy5YtnDp1ioSEBKZMmcLZs2dt1zWZTDz22GMcPnyYtWvXMmvWLCZPnizjtoUQQlyXtEtC1AzpKRKiGoxGI5s3b+a9994jJyeH0NBQ3nnnHYYNG0aPHj1ISEigR48e5OXlsWnTJgYMGMDmzZt57rnnuPfee8nNzaVZs2YMGjSo3Cd0gwYNIiwsjP79+1NcXMzYsWOZPXu2/RIVQgjhEKRdEqJmaJRSyt5BCNGQjR8/nuzsbFavXm3vUIQQQghpl0SDJH2gQgghhBBCiAZNiiIhhBBCCCFEgybD54QQQgghhBANmvQUCSGEEEIIIRo0KYqEEEIIIYQQDZoURUIIIYQQQogGTYoiIYQQQgghRIMmRZEQQgghhBCiQZOiSAghhBBCCNGgSVEkhBBCCCGEaNCkKBJCCCGEEEI0aFIUCSGEEEIIIRq0/wcsgXjMj85d+AAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "l9N2QrfFpYuD"
   },
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T11:56:55.166113Z",
     "iopub.status.busy": "2025-01-21T11:56:55.165932Z",
     "iopub.status.idle": "2025-01-21T11:56:56.752371Z",
     "shell.execute_reply": "2025-01-21T11:56:56.751630Z",
     "shell.execute_reply.started": "2025-01-21T11:56:55.166093Z"
    },
    "id": "qiN3FDcJpYuE",
    "outputId": "9c9b50e4-79d5-4027-824b-8f7ef233eea9",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     3.1086\n",
      "accuracy: 0.6434\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\", weights_only=True,map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
